jimpex 8.0.0 → 9.0.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.
- package/CHANGELOG.md +13 -0
- package/README.md +2 -2
- package/dist/app/index.d.mts +14 -0
- package/dist/app/index.d.ts +5 -4
- package/dist/app/index.js +4 -0
- package/dist/app/jimpex.d.mts +14 -0
- package/dist/app/jimpex.d.ts +3 -2
- package/dist/app/jimpex.js +225 -0
- package/dist/app/jimpex.js.map +1 -1
- package/dist/controllers/common/config.d.mts +79 -0
- package/dist/controllers/common/config.d.ts +3 -2
- package/dist/controllers/common/config.js +18 -0
- package/dist/controllers/common/config.js.map +1 -1
- package/dist/controllers/common/health.d.mts +83 -0
- package/dist/controllers/common/health.d.ts +4 -3
- package/dist/controllers/common/health.js +18 -0
- package/dist/controllers/common/health.js.map +1 -1
- package/dist/controllers/common/index.d.mts +21 -0
- package/dist/controllers/common/index.d.ts +4 -4
- package/dist/controllers/common/index.js +6 -0
- package/dist/controllers/common/statics.d.mts +215 -0
- package/dist/controllers/common/statics.d.ts +4 -4
- package/dist/controllers/common/statics.js +51 -0
- package/dist/controllers/common/statics.js.map +1 -1
- package/dist/controllers/index.d.mts +24 -0
- package/dist/controllers/index.d.ts +4 -4
- package/dist/controllers/index.js +5 -0
- package/dist/controllers/utils/gateway.d.mts +725 -0
- package/dist/controllers/utils/gateway.d.ts +4 -3
- package/dist/controllers/utils/gateway.js +152 -0
- package/dist/controllers/utils/gateway.js.map +1 -1
- package/dist/controllers/utils/index.d.mts +17 -0
- package/dist/controllers/utils/index.d.ts +4 -3
- package/dist/controllers/utils/index.js +4 -0
- package/dist/esm/app/jimpex.js +222 -1
- package/dist/esm/app/jimpex.js.map +1 -1
- package/dist/esm/{chunk-T2T6Q22Z.js → chunk-2B2CG5KL.js} +1 -1
- package/dist/esm/controllers/common/config.js +19 -1
- package/dist/esm/controllers/common/config.js.map +1 -1
- package/dist/esm/controllers/common/health.js +19 -1
- package/dist/esm/controllers/common/health.js.map +1 -1
- package/dist/esm/controllers/common/statics.js +48 -1
- package/dist/esm/controllers/common/statics.js.map +1 -1
- package/dist/esm/controllers/utils/gateway.js +153 -1
- package/dist/esm/controllers/utils/gateway.js.map +1 -1
- package/dist/esm/middlewares/common/errorHandler.js +25 -1
- package/dist/esm/middlewares/common/errorHandler.js.map +1 -1
- package/dist/esm/middlewares/common/forceHTTPS.js +13 -1
- package/dist/esm/middlewares/common/forceHTTPS.js.map +1 -1
- package/dist/esm/middlewares/common/hsts.js +22 -1
- package/dist/esm/middlewares/common/hsts.js.map +1 -1
- package/dist/esm/middlewares/html/fastHTML.js +55 -1
- package/dist/esm/middlewares/html/fastHTML.js.map +1 -1
- package/dist/esm/middlewares/html/showHTML.js +33 -1
- package/dist/esm/middlewares/html/showHTML.js.map +1 -1
- package/dist/esm/middlewares/utils/versionValidator.js +35 -1
- package/dist/esm/middlewares/utils/versionValidator.js.map +1 -1
- package/dist/esm/services/common/appError.js +48 -2
- package/dist/esm/services/common/appError.js.map +1 -1
- package/dist/esm/services/common/httpError.js +10 -1
- package/dist/esm/services/common/httpError.js.map +1 -1
- package/dist/esm/services/common/index.js +1 -1
- package/dist/esm/services/common/sendFile.js +1 -1
- package/dist/esm/services/frontend/frontendFs.js +29 -1
- package/dist/esm/services/frontend/frontendFs.js.map +1 -1
- package/dist/esm/services/frontend/index.js +1 -1
- package/dist/esm/services/html/htmlGenerator.js +51 -1
- package/dist/esm/services/html/htmlGenerator.js.map +1 -1
- package/dist/esm/services/html/index.js +1 -1
- package/dist/esm/services/http/apiClient.js +27 -1
- package/dist/esm/services/http/apiClient.js.map +1 -1
- package/dist/esm/services/http/http.js +50 -1
- package/dist/esm/services/http/http.js.map +1 -1
- package/dist/esm/services/http/index.js +1 -1
- package/dist/esm/services/http/responsesBuilder.js +49 -2
- package/dist/esm/services/http/responsesBuilder.js.map +1 -1
- package/dist/esm/services/utils/ensureBearerToken.js +17 -1
- package/dist/esm/services/utils/ensureBearerToken.js.map +1 -1
- package/dist/esm/services/utils/index.js +1 -1
- package/dist/esm/utils/fns/others.js +1 -1
- package/dist/esm/utils/fns/routes.js +1 -1
- package/dist/esm/utils/fns/routes.js.map +1 -1
- package/dist/esm/utils/fns/statuses.js +1 -1
- package/dist/esm/utils/fns/text.js +1 -1
- package/dist/esm/utils/wrappers.js +1 -1
- package/dist/{jimpex-7eaee271.d.ts → index-b2a04c78.d.ts} +9 -5
- package/dist/index-efeb437e.d.ts +1282 -0
- package/dist/index.d.mts +46 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -0
- package/dist/middlewares/common/errorHandler.d.mts +132 -0
- package/dist/middlewares/common/errorHandler.d.ts +4 -3
- package/dist/middlewares/common/errorHandler.js +24 -0
- package/dist/middlewares/common/errorHandler.js.map +1 -1
- package/dist/middlewares/common/forceHTTPS.d.mts +69 -0
- package/dist/middlewares/common/forceHTTPS.d.ts +5 -4
- package/dist/middlewares/common/forceHTTPS.js +12 -0
- package/dist/middlewares/common/forceHTTPS.js.map +1 -1
- package/dist/middlewares/common/hsts.d.mts +110 -0
- package/dist/middlewares/common/hsts.d.ts +5 -4
- package/dist/middlewares/common/hsts.js +21 -0
- package/dist/middlewares/common/hsts.js.map +1 -1
- package/dist/middlewares/common/index.d.mts +22 -0
- package/dist/middlewares/common/index.d.ts +4 -3
- package/dist/middlewares/common/index.js +6 -0
- package/dist/middlewares/html/fastHTML.d.mts +180 -0
- package/dist/middlewares/html/fastHTML.d.ts +4 -4
- package/dist/middlewares/html/fastHTML.js +54 -0
- package/dist/middlewares/html/fastHTML.js.map +1 -1
- package/dist/middlewares/html/index.d.mts +21 -0
- package/dist/middlewares/html/index.d.ts +4 -4
- package/dist/middlewares/html/index.js +5 -0
- package/dist/middlewares/html/showHTML.d.mts +127 -0
- package/dist/middlewares/html/showHTML.d.ts +4 -4
- package/dist/middlewares/html/showHTML.js +32 -0
- package/dist/middlewares/html/showHTML.js.map +1 -1
- package/dist/middlewares/index.d.mts +30 -0
- package/dist/middlewares/index.d.ts +4 -4
- package/dist/middlewares/index.js +6 -0
- package/dist/middlewares/utils/index.d.mts +20 -0
- package/dist/middlewares/utils/index.d.ts +4 -3
- package/dist/middlewares/utils/index.js +4 -0
- package/dist/middlewares/utils/versionValidator.d.mts +215 -0
- package/dist/middlewares/utils/versionValidator.d.ts +4 -3
- package/dist/middlewares/utils/versionValidator.js +34 -0
- package/dist/middlewares/utils/versionValidator.js.map +1 -1
- package/dist/services/common/appError.d.mts +139 -0
- package/dist/services/common/appError.d.ts +4 -3
- package/dist/services/common/appError.js +47 -1
- package/dist/services/common/appError.js.map +1 -1
- package/dist/services/common/httpError.d.mts +80 -0
- package/dist/services/common/httpError.d.ts +4 -3
- package/dist/services/common/httpError.js +9 -0
- package/dist/services/common/httpError.js.map +1 -1
- package/dist/services/common/index.d.mts +47 -0
- package/dist/services/common/index.d.ts +4 -4
- package/dist/services/common/index.js +4 -1
- package/dist/services/common/sendFile.d.mts +102 -0
- package/dist/services/common/sendFile.d.ts +3 -3
- package/dist/services/frontend/frontendFs.d.mts +96 -0
- package/dist/services/frontend/frontendFs.d.ts +3 -3
- package/dist/services/frontend/frontendFs.js +32 -0
- package/dist/services/frontend/frontendFs.js.map +1 -1
- package/dist/services/frontend/index.d.mts +40 -0
- package/dist/services/frontend/index.d.ts +4 -4
- package/dist/services/frontend/index.js +2 -1
- package/dist/services/html/htmlGenerator.d.mts +237 -0
- package/dist/services/html/htmlGenerator.d.ts +2 -2
- package/dist/services/html/htmlGenerator.js +50 -0
- package/dist/services/html/htmlGenerator.js.map +1 -1
- package/dist/services/html/index.d.mts +43 -0
- package/dist/services/html/index.d.ts +4 -4
- package/dist/services/html/index.js +2 -1
- package/dist/services/http/apiClient.d.mts +170 -0
- package/dist/services/http/apiClient.d.ts +4 -3
- package/dist/services/http/apiClient.js +26 -0
- package/dist/services/http/apiClient.js.map +1 -1
- package/dist/services/http/http.d.mts +176 -0
- package/dist/services/http/http.d.ts +4 -3
- package/dist/services/http/http.js +53 -0
- package/dist/services/http/http.js.map +1 -1
- package/dist/services/http/index.d.mts +51 -0
- package/dist/services/http/index.d.ts +4 -3
- package/dist/services/http/index.js +4 -1
- package/dist/services/http/responsesBuilder.d.mts +179 -0
- package/dist/services/http/responsesBuilder.d.ts +3 -2
- package/dist/services/http/responsesBuilder.js +48 -1
- package/dist/services/http/responsesBuilder.js.map +1 -1
- package/dist/services/index.d.mts +33 -0
- package/dist/services/index.d.ts +4 -4
- package/dist/services/index.js +8 -0
- package/dist/services/utils/ensureBearerToken.d.mts +158 -0
- package/dist/services/utils/ensureBearerToken.d.ts +5 -4
- package/dist/services/utils/ensureBearerToken.js +16 -0
- package/dist/services/utils/ensureBearerToken.js.map +1 -1
- package/dist/services/utils/index.d.mts +44 -0
- package/dist/services/utils/index.d.ts +4 -3
- package/dist/services/utils/index.js +2 -1
- package/dist/types/events.d.mts +14 -0
- package/dist/types/events.d.ts +5 -4
- package/dist/types/express.d.mts +10 -0
- package/dist/types/http.d.mts +79 -0
- package/dist/types/index.d.mts +14 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +9 -0
- package/dist/types/options.d.mts +14 -0
- package/dist/types/options.d.ts +4 -3
- package/dist/types/utils.d.mts +48 -0
- package/dist/types/wootils.d.mts +4 -0
- package/dist/utils/fns/index.d.mts +5 -0
- package/dist/utils/fns/index.js +7 -0
- package/dist/utils/fns/others.d.mts +17 -0
- package/dist/utils/fns/routes.d.mts +39 -0
- package/dist/utils/fns/routes.js.map +1 -1
- package/dist/utils/fns/statuses.d.mts +45 -0
- package/dist/utils/fns/statuses.js +4 -0
- package/dist/utils/fns/statuses.js.map +1 -1
- package/dist/utils/fns/text.d.mts +9 -0
- package/dist/utils/index.d.mts +19 -0
- package/dist/utils/index.d.ts +5 -4
- package/dist/utils/index.js +5 -0
- package/dist/utils/wrappers.d.mts +14 -0
- package/dist/utils/wrappers.d.ts +4 -3
- package/package.json +43 -43
- /package/dist/esm/{chunk-T2T6Q22Z.js.map → chunk-2B2CG5KL.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/services/html/htmlGenerator.ts"],"sourcesContent":["import { get } from '@homer0/object-utils';\nimport { deepAssignWithOverwrite } from '@homer0/deep-assign';\nimport { deferred, type DeferredPromise } from '@homer0/deferred';\nimport { providerCreator } from '../../utils';\nimport type { Config, Logger } from '../../types';\nimport type { FrontendFs } from '../frontend';\n/**\n * The options to customize a {@link HTMLGenerator} instance.\n *\n * @group Services/HTMLGenerator\n */\nexport type HTMLGeneratorOptions = {\n /**\n * The name of the file it should use as template.\n *\n * @default 'index.tpl.html'\n */\n template: string;\n /**\n * The name of the generated file.\n *\n * @default 'index.html'\n */\n file: string;\n /**\n * If `true`, it won't log messages on the terminal when generating the file.\n *\n * @default false\n */\n silent: boolean;\n /**\n * Whether or not to delete the tempalte after generating the file.\n *\n * @default true\n */\n deleteTemplateAfter: boolean;\n /**\n * The placeholder string where the information will be written.\n *\n * @default /\\{\\{appConfi(?:guration)?\\}\\}/\n */\n replacePlaceholder: string | RegExp;\n /**\n * A regular expression for dynamic placeholders that will be replaced by values when\n * the file is generated.\n *\n * @default /\\{\\{(.*?)\\}\\}/gi\n */\n placeholderExpression: RegExp;\n /**\n * The name of the variable that will have the information on the file.\n *\n * @default 'appConfig'\n */\n variableName: string;\n /**\n * A list of settings from the app configuration that will be used as the information to\n * inject on the file.\n *\n * @default ['features', 'version', 'postMessagesPrefix']\n */\n configKeys: string[];\n};\n/**\n * An external service that can be used to provide the values the generator will replace\n * in the template.\n *\n * @group Services/HTMLGenerator\n */\nexport type HTMLGeneratorValuesService = {\n /**\n * A function that will be called to get the values to replace in the template.\n *\n * @param options The service customization options.\n */\n getValues: (options: HTMLGeneratorOptions) => Promise<Record<string, unknown>>;\n};\n/**\n * The options to construct a {@link HTMLGenerator}.\n *\n * @group Services/HTMLGenerator\n */\nexport type HTMLGeneratorConstructorOptions = Partial<HTMLGeneratorOptions> & {\n /**\n * A dictionary with the dependencies to inject.\n */\n inject: {\n config: Config;\n logger: Logger;\n frontendFs: FrontendFs;\n /**\n * A service that can provide the values to replace in the template. If specified, the\n * values from `configKeys` will be ignored.\n */\n valuesService?: HTMLGeneratorValuesService;\n };\n};\n/**\n * Custom options for the provider that will register an instance of {@link HTMLGenerator}\n * as a service.\n *\n * @group Services/HTMLGenerator\n */\nexport type HTMLGeneratorProviderOptions = Partial<HTMLGeneratorOptions> & {\n /**\n * The name that will be used to register the service on the container. This is to allow\n * multiple \"instances\" of the service to be created.\n *\n * @default 'htmlGenerator'\n */\n serviceName?: string;\n /**\n * The name of a service that the generator will use in order to read the values that\n * will be replaced on the template. If the service is available, the values from\n * `configKeys` will be ignored.\n *\n * @default 'htmlGeneratorValues'\n */\n valuesServiceName?: string;\n};\n/**\n * This is a utility service that generates an HTML file with custom information when the\n * application is started.\n *\n * @group Services\n * @group Services/HTMLGenerator\n */\nexport class HTMLGenerator {\n /**\n * The service customization options.\n */\n protected readonly _options: HTMLGeneratorOptions;\n /**\n * The application configuration service, to get the settings specified by the\n * `configKeys` option.\n */\n protected readonly _config: Config;\n /**\n * The service that logs messages on the terminal, in case the `silent` option is `false`.\n */\n protected readonly _logger: Logger;\n /**\n * The service that interacts with the filesystem.\n */\n protected readonly _frontendFs: FrontendFs;\n /**\n * A service that can provide values to be replaced in the template.\n */\n protected readonly _valuesService?: HTMLGeneratorValuesService;\n /**\n * Whether or not the file was already generated.\n */\n protected _fileReady: boolean = false;\n /**\n * A deferred promise to return when another service asks if the file has been\n * generated. Once this sevice finishes generating the file, the promise will be\n * resolved for all implementations that hold a reference to this deferred.\n */\n protected _fileDeferred?: DeferredPromise<void>;\n /**\n * @param options The options to construct the class.\n * @throws If `valuesService` is specified but it doesn't have a `getValues`\n * method.\n */\n constructor({\n inject: { config, logger, frontendFs, valuesService },\n ...options\n }: HTMLGeneratorConstructorOptions) {\n this._config = config;\n this._logger = logger;\n this._frontendFs = frontendFs;\n this._valuesService = valuesService;\n this._options = deepAssignWithOverwrite(\n {\n template: 'index.tpl.html',\n file: 'index.html',\n silent: false,\n deleteTemplateAfter: true,\n replacePlaceholder: /\\{\\{appConfig(?:uration)?\\}\\}/,\n placeholderExpression: /\\{\\{(.*?)\\}\\}/gi,\n variableName: 'appConfig',\n configKeys: ['features', 'version', 'postMessagesPrefix'],\n },\n options,\n );\n\n if (this._valuesService && typeof this._valuesService.getValues !== 'function') {\n throw new Error('The HTMLGenerator values service must have a `getValues` method');\n }\n }\n /**\n * Gets a promise that will be resolved when the file has been generated.\n */\n whenReady(): Promise<void> {\n return this._fileDeferred ? this._fileDeferred.promise : Promise.resolve();\n }\n /**\n * Generates the HTML file.\n */\n async generateHTML(): Promise<void> {\n // The file is already generated, and since this is async, return the promise.\n if (this._fileReady) return undefined;\n // If the file is not ready, but the deferred exists, return the reference to the promise.\n if (this._fileDeferred) return this._fileDeferred.promise;\n // Create the deferred promise.\n this._fileDeferred = deferred<void>();\n const { template, deleteTemplateAfter, file, silent } = this._options;\n try {\n // Get the template.\n const templateContents = await this._frontendFs.read(template);\n // Get the values to replace.\n const values = await this._getValues();\n // Replace/process the template.\n const html = this._processHTML(templateContents, values);\n // Write it in the filesystem.\n await this._frontendFs.write(file, html);\n if (!silent) {\n this._logger.success(`The HTML file was successfully generated (${file})`);\n }\n // Delete the template, if specified by the options.\n if (deleteTemplateAfter) {\n await this._frontendFs.delete(template);\n if (!silent) {\n this._logger.info(`The HTML template was successfully removed (${template})`);\n }\n }\n\n // Switch the flag, resolve the deferred promise, and delete it.\n this._fileReady = true;\n this._fileDeferred!.resolve();\n this._fileDeferred = undefined;\n return undefined;\n } catch (error) {\n this._fileDeferred!.reject(error);\n this._fileDeferred = undefined;\n if (!silent) {\n this._logger.error('There was an error while generating the HTML');\n }\n throw error;\n }\n }\n /**\n * Gets the customization options.\n */\n get options(): Readonly<HTMLGeneratorOptions> {\n return deepAssignWithOverwrite({}, this._options);\n }\n /**\n * Helper method to get the values that will be replaced in the template. If a \"values\n * service\" was specified in the constructor, it will get the values from there,\n * otherwise, it will use the `configKeys` option to get the values from the\n * application configuration.\n */\n protected _getValues(): Promise<Record<string, unknown>> {\n if (this._valuesService) {\n return this._valuesService.getValues(this._options);\n }\n\n const { configKeys } = this._options;\n if (configKeys && configKeys.length) {\n return Promise.resolve(this._config.get(configKeys));\n }\n\n return Promise.resolve({});\n }\n /**\n * Processes the HTML template by replacing the placeholders with the actual values.\n *\n * @param template The template for the HTML file.\n * @param values The values dictionary that should be replaced in the template.\n */\n protected _processHTML(template: string, values: Record<string, unknown>) {\n const { replacePlaceholder, placeholderExpression, variableName } = this._options;\n const htmlObject = JSON.stringify(values);\n let code = template.replace(\n replacePlaceholder,\n `window.${variableName} = ${htmlObject}`,\n );\n const matches: Array<{ string: string; value: string }> = [];\n let match = placeholderExpression.exec(code);\n while (match) {\n const [string, value] = match;\n if (string && value) {\n matches.push({\n string,\n value,\n });\n }\n\n match = placeholderExpression.exec(code);\n }\n\n matches.forEach((info) => {\n code = code.replace(info.string, String(get(values, info.value)));\n });\n\n return code;\n }\n}\n/**\n * The service provider that once registered on the container will set an instance of\n * {@link HTMLGenerator} as the `htmlGenerator` service. it will also hook itself to the\n * `after-start` event of the application in order to trigger the generator of the HTML\n * file.\n *\n * @example\n *\n * <caption>Basic usage</caption>\n *\n * // Register it on the container\n * container.register(htmlGeneratorProvider);\n * // Getting access to the service instance\n * const htmlGenerator = container.get<HTMLGenerator>('htmlGenerator');\n *\n * @example\n *\n * <caption>Using with custom options</caption>\n *\n * container.register(\n * htmlGeneratorProvider({\n * serviceName: 'myHtmlGenerator',\n * valuesServiceName: 'myValuesService',\n * template: 'my-template.tpl.html',\n * }),\n * );\n *\n * @group Providers\n * @group Services/HTMLGenerator\n */\nexport const htmlGeneratorProvider = providerCreator(\n (options: HTMLGeneratorProviderOptions = {}) =>\n (app) => {\n const {\n serviceName = 'htmlGenerator',\n valuesServiceName = 'htmlGeneratorValues',\n ...rest\n } = options;\n app.set(\n serviceName,\n () =>\n new HTMLGenerator({\n inject: {\n config: app.get('config'),\n logger: app.get('logger'),\n frontendFs: app.get('frontendFs'),\n valuesService: app.try(valuesServiceName),\n },\n ...rest,\n }),\n );\n\n app.once('afterStart', () => {\n app.get<HTMLGenerator>(serviceName).generateHTML();\n });\n },\n);\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/services/html/htmlGenerator.ts"],"sourcesContent":["import { get } from '@homer0/object-utils';\nimport { deepAssignWithOverwrite } from '@homer0/deep-assign';\nimport { deferred, type DeferredPromise } from '@homer0/deferred';\nimport { providerCreator } from '../../utils';\nimport type { Config, Logger } from '../../types';\nimport type { FrontendFs } from '../frontend';\n/**\n * The options to customize a {@link HTMLGenerator} instance.\n *\n * @group Services/HTMLGenerator\n */\nexport type HTMLGeneratorOptions = {\n /**\n * The name of the file it should use as template.\n *\n * @default 'index.tpl.html'\n */\n template: string;\n /**\n * The name of the generated file.\n *\n * @default 'index.html'\n */\n file: string;\n /**\n * If `true`, it won't log messages on the terminal when generating the file.\n *\n * @default false\n */\n silent: boolean;\n /**\n * Whether or not to delete the tempalte after generating the file.\n *\n * @default true\n */\n deleteTemplateAfter: boolean;\n /**\n * The placeholder string where the information will be written.\n *\n * @default /\\{\\{appConfi(?:guration)?\\}\\}/\n */\n replacePlaceholder: string | RegExp;\n /**\n * A regular expression for dynamic placeholders that will be replaced by values when\n * the file is generated.\n *\n * @default /\\{\\{(.*?)\\}\\}/gi\n */\n placeholderExpression: RegExp;\n /**\n * The name of the variable that will have the information on the file.\n *\n * @default 'appConfig'\n */\n variableName: string;\n /**\n * A list of settings from the app configuration that will be used as the information to\n * inject on the file.\n *\n * @default ['features', 'version', 'postMessagesPrefix']\n */\n configKeys: string[];\n};\n/**\n * An external service that can be used to provide the values the generator will replace\n * in the template.\n *\n * @group Services/HTMLGenerator\n */\nexport type HTMLGeneratorValuesService = {\n /**\n * A function that will be called to get the values to replace in the template.\n *\n * @param options The service customization options.\n */\n getValues: (options: HTMLGeneratorOptions) => Promise<Record<string, unknown>>;\n};\n/**\n * The options to construct a {@link HTMLGenerator}.\n *\n * @group Services/HTMLGenerator\n */\nexport type HTMLGeneratorConstructorOptions = Partial<HTMLGeneratorOptions> & {\n /**\n * A dictionary with the dependencies to inject.\n */\n inject: {\n config: Config;\n logger: Logger;\n frontendFs: FrontendFs;\n /**\n * A service that can provide the values to replace in the template. If specified, the\n * values from `configKeys` will be ignored.\n */\n valuesService?: HTMLGeneratorValuesService;\n };\n};\n/**\n * Custom options for the provider that will register an instance of {@link HTMLGenerator}\n * as a service.\n *\n * @group Services/HTMLGenerator\n */\nexport type HTMLGeneratorProviderOptions = Partial<HTMLGeneratorOptions> & {\n /**\n * The name that will be used to register the service on the container. This is to allow\n * multiple \"instances\" of the service to be created.\n *\n * @default 'htmlGenerator'\n */\n serviceName?: string;\n /**\n * The name of a service that the generator will use in order to read the values that\n * will be replaced on the template. If the service is available, the values from\n * `configKeys` will be ignored.\n *\n * @default 'htmlGeneratorValues'\n */\n valuesServiceName?: string;\n};\n/**\n * This is a utility service that generates an HTML file with custom information when the\n * application is started.\n *\n * @group Services\n * @group Services/HTMLGenerator\n */\nexport class HTMLGenerator {\n /**\n * The service customization options.\n */\n protected readonly _options: HTMLGeneratorOptions;\n /**\n * The application configuration service, to get the settings specified by the\n * `configKeys` option.\n */\n protected readonly _config: Config;\n /**\n * The service that logs messages on the terminal, in case the `silent` option is `false`.\n */\n protected readonly _logger: Logger;\n /**\n * The service that interacts with the filesystem.\n */\n protected readonly _frontendFs: FrontendFs;\n /**\n * A service that can provide values to be replaced in the template.\n */\n protected readonly _valuesService?: HTMLGeneratorValuesService;\n /**\n * Whether or not the file was already generated.\n */\n protected _fileReady: boolean = false;\n /**\n * A deferred promise to return when another service asks if the file has been\n * generated. Once this sevice finishes generating the file, the promise will be\n * resolved for all implementations that hold a reference to this deferred.\n */\n protected _fileDeferred?: DeferredPromise<void>;\n /**\n * @param options The options to construct the class.\n * @throws If `valuesService` is specified but it doesn't have a `getValues`\n * method.\n */\n constructor({\n inject: { config, logger, frontendFs, valuesService },\n ...options\n }: HTMLGeneratorConstructorOptions) {\n this._config = config;\n this._logger = logger;\n this._frontendFs = frontendFs;\n this._valuesService = valuesService;\n this._options = deepAssignWithOverwrite(\n {\n template: 'index.tpl.html',\n file: 'index.html',\n silent: false,\n deleteTemplateAfter: true,\n replacePlaceholder: /\\{\\{appConfig(?:uration)?\\}\\}/,\n placeholderExpression: /\\{\\{(.*?)\\}\\}/gi,\n variableName: 'appConfig',\n configKeys: ['features', 'version', 'postMessagesPrefix'],\n },\n options,\n );\n\n if (this._valuesService && typeof this._valuesService.getValues !== 'function') {\n throw new Error('The HTMLGenerator values service must have a `getValues` method');\n }\n }\n /**\n * Gets a promise that will be resolved when the file has been generated.\n */\n whenReady(): Promise<void> {\n return this._fileDeferred ? this._fileDeferred.promise : Promise.resolve();\n }\n /**\n * Generates the HTML file.\n */\n async generateHTML(): Promise<void> {\n // The file is already generated, and since this is async, return the promise.\n if (this._fileReady) return undefined;\n // If the file is not ready, but the deferred exists, return the reference to the promise.\n if (this._fileDeferred) return this._fileDeferred.promise;\n // Create the deferred promise.\n this._fileDeferred = deferred<void>();\n const { template, deleteTemplateAfter, file, silent } = this._options;\n try {\n // Get the template.\n const templateContents = await this._frontendFs.read(template);\n // Get the values to replace.\n const values = await this._getValues();\n // Replace/process the template.\n const html = this._processHTML(templateContents, values);\n // Write it in the filesystem.\n await this._frontendFs.write(file, html);\n if (!silent) {\n this._logger.success(`The HTML file was successfully generated (${file})`);\n }\n // Delete the template, if specified by the options.\n if (deleteTemplateAfter) {\n await this._frontendFs.delete(template);\n if (!silent) {\n this._logger.info(`The HTML template was successfully removed (${template})`);\n }\n }\n\n // Switch the flag, resolve the deferred promise, and delete it.\n this._fileReady = true;\n this._fileDeferred!.resolve();\n this._fileDeferred = undefined;\n return undefined;\n } catch (error) {\n this._fileDeferred!.reject(error);\n this._fileDeferred = undefined;\n if (!silent) {\n this._logger.error('There was an error while generating the HTML');\n }\n throw error;\n }\n }\n /**\n * Gets the customization options.\n */\n get options(): Readonly<HTMLGeneratorOptions> {\n return deepAssignWithOverwrite({}, this._options);\n }\n /**\n * Helper method to get the values that will be replaced in the template. If a \"values\n * service\" was specified in the constructor, it will get the values from there,\n * otherwise, it will use the `configKeys` option to get the values from the\n * application configuration.\n */\n protected _getValues(): Promise<Record<string, unknown>> {\n if (this._valuesService) {\n return this._valuesService.getValues(this._options);\n }\n\n const { configKeys } = this._options;\n if (configKeys && configKeys.length) {\n return Promise.resolve(this._config.get(configKeys));\n }\n\n return Promise.resolve({});\n }\n /**\n * Processes the HTML template by replacing the placeholders with the actual values.\n *\n * @param template The template for the HTML file.\n * @param values The values dictionary that should be replaced in the template.\n */\n protected _processHTML(template: string, values: Record<string, unknown>) {\n const { replacePlaceholder, placeholderExpression, variableName } = this._options;\n const htmlObject = JSON.stringify(values);\n let code = template.replace(\n replacePlaceholder,\n `window.${variableName} = ${htmlObject}`,\n );\n const matches: Array<{ string: string; value: string }> = [];\n let match = placeholderExpression.exec(code);\n while (match) {\n const [string, value] = match;\n if (string && value) {\n matches.push({\n string,\n value,\n });\n }\n\n match = placeholderExpression.exec(code);\n }\n\n matches.forEach((info) => {\n code = code.replace(info.string, String(get(values, info.value)));\n });\n\n return code;\n }\n}\n/**\n * The service provider that once registered on the container will set an instance of\n * {@link HTMLGenerator} as the `htmlGenerator` service. it will also hook itself to the\n * `after-start` event of the application in order to trigger the generator of the HTML\n * file.\n *\n * @example\n *\n * <caption>Basic usage</caption>\n *\n * // Register it on the container\n * container.register(htmlGeneratorProvider);\n * // Getting access to the service instance\n * const htmlGenerator = container.get<HTMLGenerator>('htmlGenerator');\n *\n * @example\n *\n * <caption>Using with custom options</caption>\n *\n * container.register(\n * htmlGeneratorProvider({\n * serviceName: 'myHtmlGenerator',\n * valuesServiceName: 'myValuesService',\n * template: 'my-template.tpl.html',\n * }),\n * );\n *\n * @group Providers\n * @group Services/HTMLGenerator\n */\nexport const htmlGeneratorProvider = providerCreator(\n (options: HTMLGeneratorProviderOptions = {}) =>\n (app) => {\n const {\n serviceName = 'htmlGenerator',\n valuesServiceName = 'htmlGeneratorValues',\n ...rest\n } = options;\n app.set(\n serviceName,\n () =>\n new HTMLGenerator({\n inject: {\n config: app.get('config'),\n logger: app.get('logger'),\n frontendFs: app.get('frontendFs'),\n valuesService: app.try(valuesServiceName),\n },\n ...rest,\n }),\n );\n\n app.once('afterStart', () => {\n app.get<HTMLGenerator>(serviceName).generateHTML();\n });\n },\n);\n"],"mappings":"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","names":[]}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
__publicField
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-2B2CG5KL.js";
|
|
4
4
|
import {
|
|
5
5
|
APIClient as APIClientBase
|
|
6
6
|
} from "@homer0/api-utils";
|
|
7
7
|
import { deepAssignWithOverwrite } from "@homer0/deep-assign";
|
|
8
8
|
import { providerCreator } from "../../utils";
|
|
9
9
|
class APIClient extends APIClientBase {
|
|
10
|
+
/**
|
|
11
|
+
* @param options The options to construct the class.
|
|
12
|
+
*/
|
|
10
13
|
constructor({ inject: { http, HTTPError }, ...rest }) {
|
|
11
14
|
const { endpoints, gateway, url } = rest;
|
|
12
15
|
const useEndpoints = endpoints || gateway;
|
|
@@ -18,16 +21,33 @@ class APIClient extends APIClientBase {
|
|
|
18
21
|
...apiConfig,
|
|
19
22
|
fetchClient: http.fetch
|
|
20
23
|
});
|
|
24
|
+
/**
|
|
25
|
+
* The service that makes the requests to the API.
|
|
26
|
+
*/
|
|
21
27
|
__publicField(this, "_http");
|
|
28
|
+
/**
|
|
29
|
+
* The class to generate possible errors in the requests.
|
|
30
|
+
*/
|
|
22
31
|
__publicField(this, "_HTTPError");
|
|
32
|
+
/**
|
|
33
|
+
* The configuration of the API it uses.
|
|
34
|
+
*/
|
|
23
35
|
__publicField(this, "_apiConfig");
|
|
24
36
|
this._http = http;
|
|
25
37
|
this._HTTPError = HTTPError;
|
|
26
38
|
this._apiConfig = deepAssignWithOverwrite({}, apiConfig);
|
|
27
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* The configuration for the API.
|
|
42
|
+
*/
|
|
28
43
|
get apiConfig() {
|
|
29
44
|
return deepAssignWithOverwrite({}, this._apiConfig);
|
|
30
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Tries to obtain a message from an error caused on a failed request.
|
|
48
|
+
*
|
|
49
|
+
* @param response The response from the failed request.
|
|
50
|
+
*/
|
|
31
51
|
_getErrorMessageFromResponse(response) {
|
|
32
52
|
const res = response;
|
|
33
53
|
if (res.error)
|
|
@@ -40,6 +60,12 @@ class APIClient extends APIClientBase {
|
|
|
40
60
|
}
|
|
41
61
|
return "Unexpected error";
|
|
42
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Generates an {@link _HTTPError} from the response of a failed request.
|
|
65
|
+
*
|
|
66
|
+
* @param response The response from the failed request.
|
|
67
|
+
* @param status The status code of the response.
|
|
68
|
+
*/
|
|
43
69
|
formatError(response, status) {
|
|
44
70
|
return new this._HTTPError(this._getErrorMessageFromResponse(response), status);
|
|
45
71
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/services/http/apiClient.ts"],"sourcesContent":["import {\n APIClient as APIClientBase,\n type ErrorResponse,\n type APIClientOptions,\n type FetchClient,\n} from '@homer0/api-utils';\nimport { deepAssignWithOverwrite } from '@homer0/deep-assign';\nimport { providerCreator } from '../../utils';\nimport type { HTTP } from './http';\nimport type { HTTPErrorClass } from '../common';\n\nexport type { ErrorResponse };\n/**\n * The required settings needed to create the an API client.\n *\n * @group Services/APIClient\n */\nexport type APIClientConfig = Pick<APIClientOptions, 'url' | 'endpoints'>;\n/**\n * A dictionary of endpoints for the API client.\n * This is declared as standalone because it needs to reference them multiple times.\n *\n * @group Services/APIClient\n */\nexport type EndpointsType = APIClientOptions['endpoints'];\n/**\n * The format the settings needs to have in the application configuration in order to\n * create a valid API client.\n * The settings may include the endpoints dictionary in the `endpoints` property, or the\n * gateway `property`, with `endpoints` always having priority if both exists.\n * The reason for the `gateway` property to be valid, is in case the application also\n * implements a {@link GatewayController}, using the same property for both things will\n * help reduce the amount of duplicated definitions.\n *\n * @group Services/APIClient\n */\nexport type APIClientSettings = Omit<APIClientConfig, 'endpoints'> &\n (\n | {\n endpoints: EndpointsType;\n gateway?: EndpointsType;\n }\n | {\n endpoints?: EndpointsType;\n gateway: EndpointsType;\n }\n );\n/**\n * The options to construct a {@link APIClient}.\n *\n * @group Services/APIClient\n */\nexport type APIClientConstructorOptions = APIClientSettings & {\n /**\n * A dictionary with the dependencies to inject.\n */\n inject: {\n http: HTTP;\n HTTPError: HTTPErrorClass;\n };\n};\n/**\n * An API client for the application to use. What makes this service special is that it\n * formats received errors using the {@link _HTTPError} class, and as fetch client, it\n * uses the {@link HTTP} service, allowing the application to to internally handle all the\n * requests and responses.\n *\n * The only reason to use the class directly is if you want to subclass it, as you would\n * normally just register the {@link apiClientProvider}.\n *\n * @group Services\n * @group Services/APIClient\n */\nexport class APIClient extends APIClientBase {\n /**\n * The service that makes the requests to the API.\n */\n protected readonly _http: HTTP;\n /**\n * The class to generate possible errors in the requests.\n */\n protected readonly _HTTPError: HTTPErrorClass;\n /**\n * The configuration of the API it uses.\n */\n protected readonly _apiConfig: APIClientConfig;\n /**\n * @param options The options to construct the class.\n */\n constructor({ inject: { http, HTTPError }, ...rest }: APIClientConstructorOptions) {\n const { endpoints, gateway, url } = rest;\n const useEndpoints = (endpoints || gateway)!;\n const apiConfig = {\n url,\n endpoints: useEndpoints,\n };\n super({\n ...apiConfig,\n fetchClient: http.fetch as unknown as FetchClient,\n });\n\n this._http = http;\n this._HTTPError = HTTPError;\n this._apiConfig = deepAssignWithOverwrite({}, apiConfig);\n }\n /**\n * The configuration for the API.\n */\n get apiConfig(): Readonly<APIClientConfig> {\n return deepAssignWithOverwrite({}, this._apiConfig);\n }\n /**\n * Tries to obtain a message from an error caused on a failed request.\n *\n * @param response The response from the failed request.\n */\n protected _getErrorMessageFromResponse(response: unknown) {\n const res = response as {\n error?: string;\n data?: {\n message?: string;\n error?: string;\n };\n };\n if (res.error) return res.error;\n if (res.data) {\n if (res.data.message) return res.data.message;\n if (res.data.error) return res.data.error;\n }\n\n return 'Unexpected error';\n }\n /**\n * Generates an {@link _HTTPError} from the response of a failed request.\n *\n * @param response The response from the failed request.\n * @param status The status code of the response.\n */\n protected override formatError<ResponseType extends ErrorResponse>(\n response: ResponseType,\n status: number,\n ): Error {\n return new this._HTTPError(this._getErrorMessageFromResponse(response), status);\n }\n}\n/**\n * The options for the provider creator that registers an {@link APIClient} in the\n * container.\n * These options allow the application to register multiple instances for different APIs.\n *\n * @group Services/APIClient\n */\nexport type APIClientProviderOptions = {\n /**\n * The name of the service that will be registered into the app.\n *\n * @default 'apiClient'\n */\n serviceName?: string;\n /**\n * The name of the configuration setting that has the API information.\n *\n * @default 'api'\n */\n configSetting?: string;\n /**\n * The class the service will instantiate. It has to extend {@link APIClient}.\n *\n * @default APIClient\n */\n clientClass?: typeof APIClient;\n};\n/**\n * The provider creator to register an instance of {@link APIClient} on the container.\n *\n * @example\n *\n * <caption>Basic usage</caption>\n *\n * // Register it on the container\n * container.register(apiClientProvider);\n * // Getting access to the service instance\n * const apiClient = container.get<APIClient>('apiClient');\n *\n * @example\n *\n * <caption>With custom options</caption>\n *\n * container.register(\n * apiClientProvider({\n * serviceName: 'myApiClient',\n * configSetting: 'myApi',\n * }),\n * );\n *\n * @group Providers\n * @group Services/APIClient\n */\nexport const apiClientProvider = providerCreator(\n (options: APIClientProviderOptions = {}) =>\n (app) => {\n const defaultName = 'apiClient';\n const { serviceName = defaultName, clientClass: ClientClass = APIClient } = options;\n let { configSetting } = options;\n if (!configSetting) {\n configSetting = serviceName === defaultName ? 'api' : serviceName;\n }\n\n app.set(\n serviceName,\n () =>\n new ClientClass({\n inject: {\n http: app.get('http'),\n HTTPError: app.get('HTTPError'),\n },\n ...app.getConfig<APIClientSettings>(configSetting!),\n }),\n );\n },\n);\n"],"mappings":";;;AAAA;AAAA,EACE,aAAa;AAAA,OAIR;AACP,SAAS,+BAA+B;AACxC,SAAS,uBAAuB;AAkEzB,MAAM,kBAAkB,cAAc;AAAA,EAgB3C,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,
|
|
1
|
+
{"version":3,"sources":["../../../../src/services/http/apiClient.ts"],"sourcesContent":["import {\n APIClient as APIClientBase,\n type ErrorResponse,\n type APIClientOptions,\n type FetchClient,\n} from '@homer0/api-utils';\nimport { deepAssignWithOverwrite } from '@homer0/deep-assign';\nimport { providerCreator } from '../../utils';\nimport type { HTTP } from './http';\nimport type { HTTPErrorClass } from '../common';\n\nexport type { ErrorResponse };\n/**\n * The required settings needed to create the an API client.\n *\n * @group Services/APIClient\n */\nexport type APIClientConfig = Pick<APIClientOptions, 'url' | 'endpoints'>;\n/**\n * A dictionary of endpoints for the API client.\n * This is declared as standalone because it needs to reference them multiple times.\n *\n * @group Services/APIClient\n */\nexport type EndpointsType = APIClientOptions['endpoints'];\n/**\n * The format the settings needs to have in the application configuration in order to\n * create a valid API client.\n * The settings may include the endpoints dictionary in the `endpoints` property, or the\n * gateway `property`, with `endpoints` always having priority if both exists.\n * The reason for the `gateway` property to be valid, is in case the application also\n * implements a {@link GatewayController}, using the same property for both things will\n * help reduce the amount of duplicated definitions.\n *\n * @group Services/APIClient\n */\nexport type APIClientSettings = Omit<APIClientConfig, 'endpoints'> &\n (\n | {\n endpoints: EndpointsType;\n gateway?: EndpointsType;\n }\n | {\n endpoints?: EndpointsType;\n gateway: EndpointsType;\n }\n );\n/**\n * The options to construct a {@link APIClient}.\n *\n * @group Services/APIClient\n */\nexport type APIClientConstructorOptions = APIClientSettings & {\n /**\n * A dictionary with the dependencies to inject.\n */\n inject: {\n http: HTTP;\n HTTPError: HTTPErrorClass;\n };\n};\n/**\n * An API client for the application to use. What makes this service special is that it\n * formats received errors using the {@link _HTTPError} class, and as fetch client, it\n * uses the {@link HTTP} service, allowing the application to to internally handle all the\n * requests and responses.\n *\n * The only reason to use the class directly is if you want to subclass it, as you would\n * normally just register the {@link apiClientProvider}.\n *\n * @group Services\n * @group Services/APIClient\n */\nexport class APIClient extends APIClientBase {\n /**\n * The service that makes the requests to the API.\n */\n protected readonly _http: HTTP;\n /**\n * The class to generate possible errors in the requests.\n */\n protected readonly _HTTPError: HTTPErrorClass;\n /**\n * The configuration of the API it uses.\n */\n protected readonly _apiConfig: APIClientConfig;\n /**\n * @param options The options to construct the class.\n */\n constructor({ inject: { http, HTTPError }, ...rest }: APIClientConstructorOptions) {\n const { endpoints, gateway, url } = rest;\n const useEndpoints = (endpoints || gateway)!;\n const apiConfig = {\n url,\n endpoints: useEndpoints,\n };\n super({\n ...apiConfig,\n fetchClient: http.fetch as unknown as FetchClient,\n });\n\n this._http = http;\n this._HTTPError = HTTPError;\n this._apiConfig = deepAssignWithOverwrite({}, apiConfig);\n }\n /**\n * The configuration for the API.\n */\n get apiConfig(): Readonly<APIClientConfig> {\n return deepAssignWithOverwrite({}, this._apiConfig);\n }\n /**\n * Tries to obtain a message from an error caused on a failed request.\n *\n * @param response The response from the failed request.\n */\n protected _getErrorMessageFromResponse(response: unknown) {\n const res = response as {\n error?: string;\n data?: {\n message?: string;\n error?: string;\n };\n };\n if (res.error) return res.error;\n if (res.data) {\n if (res.data.message) return res.data.message;\n if (res.data.error) return res.data.error;\n }\n\n return 'Unexpected error';\n }\n /**\n * Generates an {@link _HTTPError} from the response of a failed request.\n *\n * @param response The response from the failed request.\n * @param status The status code of the response.\n */\n protected override formatError<ResponseType extends ErrorResponse>(\n response: ResponseType,\n status: number,\n ): Error {\n return new this._HTTPError(this._getErrorMessageFromResponse(response), status);\n }\n}\n/**\n * The options for the provider creator that registers an {@link APIClient} in the\n * container.\n * These options allow the application to register multiple instances for different APIs.\n *\n * @group Services/APIClient\n */\nexport type APIClientProviderOptions = {\n /**\n * The name of the service that will be registered into the app.\n *\n * @default 'apiClient'\n */\n serviceName?: string;\n /**\n * The name of the configuration setting that has the API information.\n *\n * @default 'api'\n */\n configSetting?: string;\n /**\n * The class the service will instantiate. It has to extend {@link APIClient}.\n *\n * @default APIClient\n */\n clientClass?: typeof APIClient;\n};\n/**\n * The provider creator to register an instance of {@link APIClient} on the container.\n *\n * @example\n *\n * <caption>Basic usage</caption>\n *\n * // Register it on the container\n * container.register(apiClientProvider);\n * // Getting access to the service instance\n * const apiClient = container.get<APIClient>('apiClient');\n *\n * @example\n *\n * <caption>With custom options</caption>\n *\n * container.register(\n * apiClientProvider({\n * serviceName: 'myApiClient',\n * configSetting: 'myApi',\n * }),\n * );\n *\n * @group Providers\n * @group Services/APIClient\n */\nexport const apiClientProvider = providerCreator(\n (options: APIClientProviderOptions = {}) =>\n (app) => {\n const defaultName = 'apiClient';\n const { serviceName = defaultName, clientClass: ClientClass = APIClient } = options;\n let { configSetting } = options;\n if (!configSetting) {\n configSetting = serviceName === defaultName ? 'api' : serviceName;\n }\n\n app.set(\n serviceName,\n () =>\n new ClientClass({\n inject: {\n http: app.get('http'),\n HTTPError: app.get('HTTPError'),\n },\n ...app.getConfig<APIClientSettings>(configSetting!),\n }),\n );\n },\n);\n"],"mappings":";;;AAAA;AAAA,EACE,aAAa;AAAA,OAIR;AACP,SAAS,+BAA+B;AACxC,SAAS,uBAAuB;AAkEzB,MAAM,kBAAkB,cAAc;AAAA;AAAA;AAAA;AAAA,EAgB3C,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,GAAG,GAAG,KAAK,GAAgC;AACjF,UAAM,EAAE,WAAW,SAAS,IAAI,IAAI;AACpC,UAAM,eAAgB,aAAa;AACnC,UAAM,YAAY;AAAA,MAChB;AAAA,MACA,WAAW;AAAA,IACb;AACA,UAAM;AAAA,MACJ,GAAG;AAAA,MACH,aAAa,KAAK;AAAA,IACpB,CAAC;AAtBH;AAAA;AAAA;AAAA,wBAAmB;AAInB;AAAA;AAAA;AAAA,wBAAmB;AAInB;AAAA;AAAA;AAAA,wBAAmB;AAgBjB,SAAK,QAAQ;AACb,SAAK,aAAa;AAClB,SAAK,aAAa,wBAAwB,CAAC,GAAG,SAAS;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA,EAIA,IAAI,YAAuC;AACzC,WAAO,wBAAwB,CAAC,GAAG,KAAK,UAAU;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMU,6BAA6B,UAAmB;AACxD,UAAM,MAAM;AAOZ,QAAI,IAAI;AAAO,aAAO,IAAI;AAC1B,QAAI,IAAI,MAAM;AACZ,UAAI,IAAI,KAAK;AAAS,eAAO,IAAI,KAAK;AACtC,UAAI,IAAI,KAAK;AAAO,eAAO,IAAI,KAAK;AAAA,IACtC;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOmB,YACjB,UACA,QACO;AACP,WAAO,IAAI,KAAK,WAAW,KAAK,6BAA6B,QAAQ,GAAG,MAAM;AAAA,EAChF;AACF;AAsDO,MAAM,oBAAoB;AAAA,EAC/B,CAAC,UAAoC,CAAC,MACpC,CAAC,QAAQ;AACP,UAAM,cAAc;AACpB,UAAM,EAAE,cAAc,aAAa,aAAa,cAAc,UAAU,IAAI;AAC5E,QAAI,EAAE,cAAc,IAAI;AACxB,QAAI,CAAC,eAAe;AAClB,sBAAgB,gBAAgB,cAAc,QAAQ;AAAA,IACxD;AAEA,QAAI;AAAA,MACF;AAAA,MACA,MACE,IAAI,YAAY;AAAA,QACd,QAAQ;AAAA,UACN,MAAM,IAAI,IAAI,MAAM;AAAA,UACpB,WAAW,IAAI,IAAI,WAAW;AAAA,QAChC;AAAA,QACA,GAAG,IAAI,UAA6B,aAAc;AAAA,MACpD,CAAC;AAAA,IACL;AAAA,EACF;AACJ;","names":[]}
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
__publicField
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-2B2CG5KL.js";
|
|
4
4
|
import fetch from "node-fetch";
|
|
5
5
|
import urijs from "urijs";
|
|
6
6
|
import { deepAssignWithOverwrite } from "@homer0/deep-assign";
|
|
7
7
|
import { provider } from "../../utils";
|
|
8
8
|
class HTTP {
|
|
9
|
+
/**
|
|
10
|
+
* @param options The options to construct the class.
|
|
11
|
+
*/
|
|
9
12
|
constructor({ inject: { logger }, ...options }) {
|
|
13
|
+
/**
|
|
14
|
+
* The service used to log information in the terminal.
|
|
15
|
+
*/
|
|
10
16
|
__publicField(this, "_logger");
|
|
17
|
+
/**
|
|
18
|
+
* The service customization options.
|
|
19
|
+
*/
|
|
11
20
|
__publicField(this, "_options");
|
|
12
21
|
this._logger = logger;
|
|
13
22
|
this._options = deepAssignWithOverwrite(
|
|
@@ -18,6 +27,12 @@ class HTTP {
|
|
|
18
27
|
);
|
|
19
28
|
this.fetch = this.fetch.bind(this);
|
|
20
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Makes a fetch request.
|
|
32
|
+
*
|
|
33
|
+
* @param url The URL to fetch.
|
|
34
|
+
* @param options The custom options for the request.
|
|
35
|
+
*/
|
|
21
36
|
async fetch(url, options = {}) {
|
|
22
37
|
let useURL = url;
|
|
23
38
|
if (options.qs) {
|
|
@@ -52,6 +67,11 @@ class HTTP {
|
|
|
52
67
|
}
|
|
53
68
|
return response;
|
|
54
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Tries to get the IP address from a given request.
|
|
72
|
+
*
|
|
73
|
+
* @param req The request from which it will try to obtain the IP address.
|
|
74
|
+
*/
|
|
55
75
|
getIPFromRequest(req) {
|
|
56
76
|
const headerValue = req.headers["x-forwarded-for"];
|
|
57
77
|
if (headerValue) {
|
|
@@ -59,6 +79,13 @@ class HTTP {
|
|
|
59
79
|
}
|
|
60
80
|
return req?.connection?.remoteAddress || req?.socket?.remoteAddress;
|
|
61
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Creates a dictionary with all the custom headers a request has. By custom header it
|
|
84
|
+
* means all the headers which name start with `x-`.
|
|
85
|
+
*
|
|
86
|
+
* @param req The request from which it will try to get the headers.
|
|
87
|
+
* @param options The options to customize the behavior with certain headers.
|
|
88
|
+
*/
|
|
62
89
|
getCustomHeadersFromRequest(req, options = {}) {
|
|
63
90
|
const { includeXForwardedHeaders = false } = options;
|
|
64
91
|
return Object.keys(req.headers).reduce((acc, headerName) => {
|
|
@@ -68,6 +95,14 @@ class HTTP {
|
|
|
68
95
|
return acc;
|
|
69
96
|
}, {});
|
|
70
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* It takes a dictionary of headers and normalize the names so each word will start with
|
|
100
|
+
* an upper case character. This is helpful in case you added custom headers and didn't
|
|
101
|
+
* care about the casing, or when copying headers from a server request, as they all
|
|
102
|
+
* come tranformed into lower case.
|
|
103
|
+
*
|
|
104
|
+
* @param headers The dictionary of headers to normalize.
|
|
105
|
+
*/
|
|
71
106
|
normalizeHeaders(headers) {
|
|
72
107
|
return Object.keys(headers).reduce((acc, name) => {
|
|
73
108
|
const newName = name.split("-").map((part) => part.replace(/^(\w)/, (_, letter) => letter.toUpperCase())).join("-");
|
|
@@ -75,9 +110,18 @@ class HTTP {
|
|
|
75
110
|
return acc;
|
|
76
111
|
}, {});
|
|
77
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* The customization options.
|
|
115
|
+
*/
|
|
78
116
|
get options() {
|
|
79
117
|
return deepAssignWithOverwrite({}, this._options);
|
|
80
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Logs a request information into the terminal.
|
|
121
|
+
*
|
|
122
|
+
* @param url The request URL.
|
|
123
|
+
* @param options The options for the request.
|
|
124
|
+
*/
|
|
81
125
|
_logRequest(url, options) {
|
|
82
126
|
const { method, headers } = options;
|
|
83
127
|
const prefix = "REQUEST> ";
|
|
@@ -93,6 +137,11 @@ class HTTP {
|
|
|
93
137
|
}
|
|
94
138
|
this._logger.info(lines);
|
|
95
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Logs a response information into the terminal.
|
|
142
|
+
*
|
|
143
|
+
* @param response The response to log.
|
|
144
|
+
*/
|
|
96
145
|
_logResponse(response) {
|
|
97
146
|
const prefix = "RESPONSE> ";
|
|
98
147
|
const lines = [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/services/http/http.ts"],"sourcesContent":["import fetch, { type RequestInit, type BodyInit } from 'node-fetch';\nimport urijs from 'urijs';\nimport { deepAssignWithOverwrite } from '@homer0/deep-assign';\nimport { provider } from '../../utils';\nimport { Logger, Request, HTTPResponse } from '../../types';\n/**\n * The options to customize the service.\n *\n * @group Services/HTTP\n */\nexport type HTTPOptions = {\n /**\n * Whether or not the service should log the requests and their responses.\n */\n logRequests?: boolean;\n};\n/**\n * The options to construct a {@link HTTP}.\n *\n * @group Services/HTTP\n */\nexport type HTTPContructorOptions = Partial<HTTPOptions> & {\n /**\n * A dictionary with the dependencies to inject.\n */\n inject: {\n logger: Logger;\n };\n};\n/**\n * The allowed formats for the body of a request.\n *\n * @group Services/HTTP\n */\nexport type HTTPFetchBody = string | Record<string | number, unknown> | BodyInit;\n/**\n * The options for a request.\n *\n * @group Services/HTTP\n */\nexport type HTTPFetchOptions = {\n /**\n * The HTTP method.\n *\n * @default 'GET'\n */\n method?: string;\n /**\n * The headers dictionary.\n */\n headers?: Record<string, string>;\n /**\n * The body of the request.\n */\n body?: HTTPFetchBody;\n /**\n * A dictionary of query string parameters.\n */\n qs?: Record<string, unknown>;\n /**\n * A request object generated by the application. This can be used to copy information\n * like custom headers or the IP (for the X-Forwarded-For header).\n */\n req?: Request;\n};\n/**\n * The options for the method that extracts custom headers from a request.\n *\n * @group Services/HTTP\n */\nexport type GetCustomHeadersFromRequestOptions = {\n /**\n * Since the method considers all headers that start with `x-` as custom headers,\n * setting this to `false` is the only way to exclude `x-forwarded-` headers from the\n * list.\n *\n * @default false\n */\n includeXForwardedHeaders?: boolean;\n};\n/**\n * A set of utilities to work with HTTP requests and responses.\n *\n * @group Services\n * @group Services/HTTP\n */\nexport class HTTP {\n /**\n * The service used to log information in the terminal.\n */\n protected readonly _logger: Logger;\n /**\n * The service customization options.\n */\n protected readonly _options: HTTPOptions;\n /**\n * @param options The options to construct the class.\n */\n constructor({ inject: { logger }, ...options }: HTTPContructorOptions) {\n this._logger = logger;\n this._options = deepAssignWithOverwrite(\n {\n logRequests: false,\n },\n options,\n );\n\n this.fetch = this.fetch.bind(this);\n }\n /**\n * Makes a fetch request.\n *\n * @param url The URL to fetch.\n * @param options The custom options for the request.\n */\n async fetch(url: string, options: HTTPFetchOptions = {}): Promise<HTTPResponse> {\n let useURL = url;\n if (options.qs) {\n useURL = urijs(url).query(options.qs).toString();\n }\n\n const fetchOptions: RequestInit = {\n method: (options.method || 'get').toUpperCase(),\n body: options.body as BodyInit,\n };\n\n let defaultHeaders: Record<string, string> | undefined;\n if (options.req) {\n defaultHeaders = this.getCustomHeadersFromRequest(options.req);\n const ip = this.getIPFromRequest(options.req);\n if (ip) {\n defaultHeaders['x-forwarded-for'] = ip;\n }\n }\n const headers = {\n ...defaultHeaders,\n ...options.headers,\n };\n\n if (Object.keys(headers).length) {\n fetchOptions.headers = this.normalizeHeaders(headers);\n }\n\n const { logRequests } = this._options;\n if (logRequests) {\n this._logRequest(useURL, fetchOptions);\n }\n\n const response = await fetch(useURL, fetchOptions);\n if (logRequests) {\n this._logResponse(response);\n }\n\n return response;\n }\n /**\n * Tries to get the IP address from a given request.\n *\n * @param req The request from which it will try to obtain the IP address.\n */\n getIPFromRequest(req: Request): string | undefined {\n const headerValue = req.headers['x-forwarded-for'];\n if (headerValue) {\n return String(headerValue);\n }\n\n return req?.connection?.remoteAddress || req?.socket?.remoteAddress;\n }\n /**\n * Creates a dictionary with all the custom headers a request has. By custom header it\n * means all the headers which name start with `x-`.\n *\n * @param req The request from which it will try to get the headers.\n * @param options The options to customize the behavior with certain headers.\n */\n getCustomHeadersFromRequest(\n req: Request,\n options: GetCustomHeadersFromRequestOptions = {},\n ): Record<string, string> {\n const { includeXForwardedHeaders = false } = options;\n return Object.keys(req.headers).reduce<Record<string, string>>((acc, headerName) => {\n if (\n headerName.startsWith('x-') &&\n (includeXForwardedHeaders || !headerName.startsWith('x-forwarded-'))\n ) {\n acc[headerName] = req.headers[headerName] as string;\n }\n return acc;\n }, {});\n }\n /**\n * It takes a dictionary of headers and normalize the names so each word will start with\n * an upper case character. This is helpful in case you added custom headers and didn't\n * care about the casing, or when copying headers from a server request, as they all\n * come tranformed into lower case.\n *\n * @param headers The dictionary of headers to normalize.\n */\n normalizeHeaders(headers: Record<string, string>): Record<string, string> {\n return Object.keys(headers).reduce<Record<string, string>>((acc, name) => {\n const newName = name\n .split('-')\n .map((part) => part.replace(/^(\\w)/, (_, letter) => letter.toUpperCase()))\n .join('-');\n\n acc[newName] = headers[name]!;\n return acc;\n }, {});\n }\n /**\n * The customization options.\n */\n get options(): Readonly<HTTPOptions> {\n return deepAssignWithOverwrite({}, this._options);\n }\n /**\n * Logs a request information into the terminal.\n *\n * @param url The request URL.\n * @param options The options for the request.\n */\n protected _logRequest(url: string, options: RequestInit): void {\n const { method, headers } = options;\n const prefix = 'REQUEST> ';\n const lines = ['--->>', `${prefix}${method} ${url}`];\n if (headers) {\n Object.keys(headers).forEach((header) => {\n const value = headers[header as keyof typeof headers];\n lines.push(`${prefix}${header}: ${value}`);\n });\n }\n\n if (options.body) {\n lines.push(`${prefix}body: \"${options.body}\"`);\n }\n\n this._logger.info(lines);\n }\n /**\n * Logs a response information into the terminal.\n *\n * @param response The response to log.\n */\n protected _logResponse(response: HTTPResponse) {\n const prefix = 'RESPONSE> ';\n const lines = [\n '<<---',\n `${prefix}${response.url}`,\n `${prefix}status: ${response.status}`,\n ];\n\n response.headers.forEach((value, header) => {\n lines.push(`${prefix}${header}: ${value}`);\n });\n\n this._logger.info(lines);\n }\n}\n/**\n * The service provider that once registered on the container will set an instance of\n * {@link HTTP} as the `http` service. The provider also checks the `debug.logRequests`\n * setting on the application configuration in order to enable or not the logging of\n * requests/responses.\n *\n * @example\n *\n * // Register it on the container\n * container.register(httpProvider);\n * // Getting access to the service instance\n * const http = container.get<HTTP>('http');\n *\n * @group Providers\n * @group Services/HTTP\n */\nexport const httpProvider = provider((app) => {\n app.set('http', () => {\n const config = app.getConfig();\n const logRequests = config.get<boolean | undefined>('debug.logRequests') === true;\n return new HTTP({\n inject: {\n logger: app.get('logger'),\n },\n logRequests,\n });\n });\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/services/http/http.ts"],"sourcesContent":["import fetch, { type RequestInit, type BodyInit } from 'node-fetch';\nimport urijs from 'urijs';\nimport { deepAssignWithOverwrite } from '@homer0/deep-assign';\nimport { provider } from '../../utils';\nimport { Logger, Request, HTTPResponse } from '../../types';\n/**\n * The options to customize the service.\n *\n * @group Services/HTTP\n */\nexport type HTTPOptions = {\n /**\n * Whether or not the service should log the requests and their responses.\n */\n logRequests?: boolean;\n};\n/**\n * The options to construct a {@link HTTP}.\n *\n * @group Services/HTTP\n */\nexport type HTTPContructorOptions = Partial<HTTPOptions> & {\n /**\n * A dictionary with the dependencies to inject.\n */\n inject: {\n logger: Logger;\n };\n};\n/**\n * The allowed formats for the body of a request.\n *\n * @group Services/HTTP\n */\nexport type HTTPFetchBody = string | Record<string | number, unknown> | BodyInit;\n/**\n * The options for a request.\n *\n * @group Services/HTTP\n */\nexport type HTTPFetchOptions = {\n /**\n * The HTTP method.\n *\n * @default 'GET'\n */\n method?: string;\n /**\n * The headers dictionary.\n */\n headers?: Record<string, string>;\n /**\n * The body of the request.\n */\n body?: HTTPFetchBody;\n /**\n * A dictionary of query string parameters.\n */\n qs?: Record<string, unknown>;\n /**\n * A request object generated by the application. This can be used to copy information\n * like custom headers or the IP (for the X-Forwarded-For header).\n */\n req?: Request;\n};\n/**\n * The options for the method that extracts custom headers from a request.\n *\n * @group Services/HTTP\n */\nexport type GetCustomHeadersFromRequestOptions = {\n /**\n * Since the method considers all headers that start with `x-` as custom headers,\n * setting this to `false` is the only way to exclude `x-forwarded-` headers from the\n * list.\n *\n * @default false\n */\n includeXForwardedHeaders?: boolean;\n};\n/**\n * A set of utilities to work with HTTP requests and responses.\n *\n * @group Services\n * @group Services/HTTP\n */\nexport class HTTP {\n /**\n * The service used to log information in the terminal.\n */\n protected readonly _logger: Logger;\n /**\n * The service customization options.\n */\n protected readonly _options: HTTPOptions;\n /**\n * @param options The options to construct the class.\n */\n constructor({ inject: { logger }, ...options }: HTTPContructorOptions) {\n this._logger = logger;\n this._options = deepAssignWithOverwrite(\n {\n logRequests: false,\n },\n options,\n );\n\n this.fetch = this.fetch.bind(this);\n }\n /**\n * Makes a fetch request.\n *\n * @param url The URL to fetch.\n * @param options The custom options for the request.\n */\n async fetch(url: string, options: HTTPFetchOptions = {}): Promise<HTTPResponse> {\n let useURL = url;\n if (options.qs) {\n useURL = urijs(url).query(options.qs).toString();\n }\n\n const fetchOptions: RequestInit = {\n method: (options.method || 'get').toUpperCase(),\n body: options.body as BodyInit,\n };\n\n let defaultHeaders: Record<string, string> | undefined;\n if (options.req) {\n defaultHeaders = this.getCustomHeadersFromRequest(options.req);\n const ip = this.getIPFromRequest(options.req);\n if (ip) {\n defaultHeaders['x-forwarded-for'] = ip;\n }\n }\n const headers = {\n ...defaultHeaders,\n ...options.headers,\n };\n\n if (Object.keys(headers).length) {\n fetchOptions.headers = this.normalizeHeaders(headers);\n }\n\n const { logRequests } = this._options;\n if (logRequests) {\n this._logRequest(useURL, fetchOptions);\n }\n\n const response = await fetch(useURL, fetchOptions);\n if (logRequests) {\n this._logResponse(response);\n }\n\n return response;\n }\n /**\n * Tries to get the IP address from a given request.\n *\n * @param req The request from which it will try to obtain the IP address.\n */\n getIPFromRequest(req: Request): string | undefined {\n const headerValue = req.headers['x-forwarded-for'];\n if (headerValue) {\n return String(headerValue);\n }\n\n return req?.connection?.remoteAddress || req?.socket?.remoteAddress;\n }\n /**\n * Creates a dictionary with all the custom headers a request has. By custom header it\n * means all the headers which name start with `x-`.\n *\n * @param req The request from which it will try to get the headers.\n * @param options The options to customize the behavior with certain headers.\n */\n getCustomHeadersFromRequest(\n req: Request,\n options: GetCustomHeadersFromRequestOptions = {},\n ): Record<string, string> {\n const { includeXForwardedHeaders = false } = options;\n return Object.keys(req.headers).reduce<Record<string, string>>((acc, headerName) => {\n if (\n headerName.startsWith('x-') &&\n (includeXForwardedHeaders || !headerName.startsWith('x-forwarded-'))\n ) {\n acc[headerName] = req.headers[headerName] as string;\n }\n return acc;\n }, {});\n }\n /**\n * It takes a dictionary of headers and normalize the names so each word will start with\n * an upper case character. This is helpful in case you added custom headers and didn't\n * care about the casing, or when copying headers from a server request, as they all\n * come tranformed into lower case.\n *\n * @param headers The dictionary of headers to normalize.\n */\n normalizeHeaders(headers: Record<string, string>): Record<string, string> {\n return Object.keys(headers).reduce<Record<string, string>>((acc, name) => {\n const newName = name\n .split('-')\n .map((part) => part.replace(/^(\\w)/, (_, letter) => letter.toUpperCase()))\n .join('-');\n\n acc[newName] = headers[name]!;\n return acc;\n }, {});\n }\n /**\n * The customization options.\n */\n get options(): Readonly<HTTPOptions> {\n return deepAssignWithOverwrite({}, this._options);\n }\n /**\n * Logs a request information into the terminal.\n *\n * @param url The request URL.\n * @param options The options for the request.\n */\n protected _logRequest(url: string, options: RequestInit): void {\n const { method, headers } = options;\n const prefix = 'REQUEST> ';\n const lines = ['--->>', `${prefix}${method} ${url}`];\n if (headers) {\n Object.keys(headers).forEach((header) => {\n const value = headers[header as keyof typeof headers];\n lines.push(`${prefix}${header}: ${value}`);\n });\n }\n\n if (options.body) {\n lines.push(`${prefix}body: \"${options.body}\"`);\n }\n\n this._logger.info(lines);\n }\n /**\n * Logs a response information into the terminal.\n *\n * @param response The response to log.\n */\n protected _logResponse(response: HTTPResponse) {\n const prefix = 'RESPONSE> ';\n const lines = [\n '<<---',\n `${prefix}${response.url}`,\n `${prefix}status: ${response.status}`,\n ];\n\n response.headers.forEach((value, header) => {\n lines.push(`${prefix}${header}: ${value}`);\n });\n\n this._logger.info(lines);\n }\n}\n/**\n * The service provider that once registered on the container will set an instance of\n * {@link HTTP} as the `http` service. The provider also checks the `debug.logRequests`\n * setting on the application configuration in order to enable or not the logging of\n * requests/responses.\n *\n * @example\n *\n * // Register it on the container\n * container.register(httpProvider);\n * // Getting access to the service instance\n * const http = container.get<HTTP>('http');\n *\n * @group Providers\n * @group Services/HTTP\n */\nexport const httpProvider = provider((app) => {\n app.set('http', () => {\n const config = app.getConfig();\n const logRequests = config.get<boolean | undefined>('debug.logRequests') === true;\n return new HTTP({\n inject: {\n logger: app.get('logger'),\n },\n logRequests,\n });\n });\n});\n"],"mappings":"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","names":[]}
|
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
import {
|
|
2
2
|
__publicField
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-2B2CG5KL.js";
|
|
4
4
|
import { provider } from "../../utils";
|
|
5
5
|
const DEFAULT_CLOSE_DELAY_FOR_POST_MESSAGE = 700;
|
|
6
6
|
class ResponsesBuilder {
|
|
7
|
+
/**
|
|
8
|
+
* @param options The options to construct the service.
|
|
9
|
+
*/
|
|
7
10
|
constructor({ inject: { config, statuses } }) {
|
|
11
|
+
/**
|
|
12
|
+
* The application configuration, to get the `version` and the `postMessagesPrefix`.
|
|
13
|
+
*/
|
|
8
14
|
__publicField(this, "_config");
|
|
15
|
+
/**
|
|
16
|
+
* The uility service to get HTTP status codes.
|
|
17
|
+
*/
|
|
9
18
|
__publicField(this, "_statuses");
|
|
10
19
|
this._config = config;
|
|
11
20
|
this._statuses = statuses;
|
|
12
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Generates and send an HTML response that emits a post message.
|
|
24
|
+
* The post message will be prefixed with the value of the configuration setting
|
|
25
|
+
* `postMessagesPrefix`.
|
|
26
|
+
*
|
|
27
|
+
* @param options The options to customize how the HTML is generated.
|
|
28
|
+
*/
|
|
13
29
|
htmlPostMessage(options) {
|
|
14
30
|
const {
|
|
15
31
|
res,
|
|
@@ -40,6 +56,23 @@ class ResponsesBuilder {
|
|
|
40
56
|
res.write(html);
|
|
41
57
|
res.end();
|
|
42
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Generates and sends a JSON response.
|
|
61
|
+
*
|
|
62
|
+
* @param options The options to customize how the JSON is generated.
|
|
63
|
+
* @example
|
|
64
|
+
*
|
|
65
|
+
* <caption>The generated looks like this.</caption>
|
|
66
|
+
*
|
|
67
|
+
* {
|
|
68
|
+
* metadata: {
|
|
69
|
+
* version: 'dev',
|
|
70
|
+
* status: 200,
|
|
71
|
+
* },
|
|
72
|
+
* data: { foo: 'bar' },
|
|
73
|
+
* }
|
|
74
|
+
*
|
|
75
|
+
*/
|
|
43
76
|
json(options) {
|
|
44
77
|
const { res, data, status, metadata = {} } = options;
|
|
45
78
|
const useStatus = typeof status === "undefined" ? this._statuses("ok") : this._normalizeStatus(status);
|
|
@@ -54,6 +87,13 @@ class ResponsesBuilder {
|
|
|
54
87
|
});
|
|
55
88
|
res.end();
|
|
56
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Generates a basic HTML template for the service to use when generating a post message
|
|
92
|
+
* response.
|
|
93
|
+
*
|
|
94
|
+
* @param title The HTML `<title />` attribute.
|
|
95
|
+
* @param code Javascript code to be wrapped on a `<script />` tag.
|
|
96
|
+
*/
|
|
57
97
|
_htmlTemplate(title, code) {
|
|
58
98
|
return `
|
|
59
99
|
<!DOCTYPE html>
|
|
@@ -67,11 +107,18 @@ class ResponsesBuilder {
|
|
|
67
107
|
<body>
|
|
68
108
|
<script type="text/javascript">
|
|
69
109
|
${code}
|
|
70
|
-
|
|
110
|
+
</script>
|
|
71
111
|
</body>
|
|
72
112
|
</html>
|
|
73
113
|
`;
|
|
74
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Utility method used to make sure a recevied status is a valid status code. If the
|
|
117
|
+
* status is a string, the method will try to find the code from the `statuses` package.
|
|
118
|
+
*
|
|
119
|
+
* @param status The status to normalize.
|
|
120
|
+
* @returns If `status` is a string, but there's no valid code, it will return 200.
|
|
121
|
+
*/
|
|
75
122
|
_normalizeStatus(status) {
|
|
76
123
|
let useStatus;
|
|
77
124
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/services/http/responsesBuilder.ts"],"sourcesContent":["import type { Config, Response } from '../../types';\nimport { provider, type Statuses } from '../../utils';\n/**\n * The options to construct a {@link ResponsesBuilder}.\n *\n * @group Services/ResponsesBuilder\n */\nexport type ResponsesBuilderConstructorOptions = {\n /**\n * A dictionary with the dependencies to inject.\n */\n inject: {\n config: Config;\n statuses: Statuses;\n };\n};\n/**\n * The options to build a response for a post message.\n *\n * @group Services/ResponsesBuilder\n */\nexport type HTMLPostMessageResponseOptions = {\n /**\n * The response object generated by the application.\n */\n res: Response;\n /**\n * The title for the HTML.\n */\n title: string;\n /**\n * The contents of the post message.\n */\n message: string;\n /**\n * The status code for the response.\n *\n * @default 200\n */\n status?: number | string;\n /**\n * From which object will the `postMessage` method called from.\n *\n * @default 'window.opener'\n */\n target?: string;\n /**\n * Whether or not to call `window.close` after sending the message.\n *\n * @default true\n */\n close?: boolean;\n /**\n * In case `close` is set to `true`, this option is to specify how many milliseconds\n * should be waited before closing the window.\n *\n * @default 700\n */\n closeDelay?: number;\n};\n/**\n * The options to build a reponse for JSON.\n *\n * @group Services/ResponsesBuilder\n */\nexport type JSONResponseOptions = {\n /**\n * The response object generated by the application.\n */\n res: Response;\n /**\n * The actual data for the response. It will be added in a `data` property.\n */\n data: unknown;\n /**\n * The status code for the response.\n *\n * @default 200\n */\n status?: number | string;\n /**\n * Extra information to include inside the `metadata` object.\n */\n metadata?: object;\n};\n/**\n * The default time to wait before closing a window, in case it's enabled.\n */\nconst DEFAULT_CLOSE_DELAY_FOR_POST_MESSAGE = 700;\n/**\n * A utility service to build responses.\n *\n * @group Services\n * @group Services/ResponsesBuilder\n */\nexport class ResponsesBuilder {\n /**\n * The application configuration, to get the `version` and the `postMessagesPrefix`.\n */\n protected readonly _config: Config;\n /**\n * The uility service to get HTTP status codes.\n */\n protected readonly _statuses: Statuses;\n /**\n * @param options The options to construct the service.\n */\n constructor({ inject: { config, statuses } }: ResponsesBuilderConstructorOptions) {\n this._config = config;\n this._statuses = statuses;\n }\n /**\n * Generates and send an HTML response that emits a post message.\n * The post message will be prefixed with the value of the configuration setting\n * `postMessagesPrefix`.\n *\n * @param options The options to customize how the HTML is generated.\n */\n htmlPostMessage(options: HTMLPostMessageResponseOptions): void {\n const {\n res,\n title,\n message,\n status,\n target = 'window.opener',\n close = true,\n closeDelay = DEFAULT_CLOSE_DELAY_FOR_POST_MESSAGE,\n } = options;\n const prefix = this._config.get<string | undefined>('postMessagesPrefix') ?? '';\n const closeCode = close\n ? `setTimeout(function() { window.close(); }, ${closeDelay});`\n : '';\n\n const html = this._htmlTemplate(\n title,\n `\n (function() {\n if (${target}) {\n ${target}.postMessage('${prefix}${message}', '*');\n ${closeCode}\n }\n })();\n `,\n );\n\n const useStatus =\n typeof status === 'undefined'\n ? (this._statuses('ok') as number)\n : this._normalizeStatus(status);\n\n res.setHeader('Content-Type', 'text/html');\n res.setHeader('Cache-Control', 'no-cache, max-age=0, must-revalidate, no-store');\n res.status(useStatus);\n res.write(html);\n res.end();\n }\n /**\n * Generates and sends a JSON response.\n *\n * @param options The options to customize how the JSON is generated.\n * @example\n *\n * <caption>The generated looks like this.</caption>\n *\n * {\n * metadata: {\n * version: 'dev',\n * status: 200,\n * },\n * data: { foo: 'bar' },\n * }\n *\n */\n json(options: JSONResponseOptions): void {\n const { res, data, status, metadata = {} } = options;\n const useStatus =\n typeof status === 'undefined'\n ? (this._statuses('ok') as number)\n : this._normalizeStatus(status);\n\n res.status(useStatus);\n res.json({\n metadata: {\n version: this._config.get<string>('version'),\n status: useStatus,\n ...metadata,\n },\n data,\n });\n res.end();\n }\n /**\n * Generates a basic HTML template for the service to use when generating a post message\n * response.\n *\n * @param title The HTML `<title />` attribute.\n * @param code Javascript code to be wrapped on a `<script />` tag.\n */\n protected _htmlTemplate(title: string, code: string): string {\n return `\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${title}</title>\n </head>\n <body>\n <script type=\"text/javascript\">\n ${code}\n </script>\n </body>\n </html>\n `;\n }\n /**\n * Utility method used to make sure a recevied status is a valid status code. If the\n * status is a string, the method will try to find the code from the `statuses` package.\n *\n * @param status The status to normalize.\n * @returns If `status` is a string, but there's no valid code, it will return 200.\n */\n protected _normalizeStatus(status: number | string): number {\n let useStatus: number;\n try {\n if (typeof status === 'string') {\n useStatus = this._statuses(status) as number;\n } else {\n this._statuses(status);\n useStatus = status;\n }\n } catch (_) {\n useStatus = this._statuses('ok') as number;\n }\n\n return useStatus;\n }\n}\n/**\n * The service provider that once registered on the container will set an instance of\n * {@link ResponsesBuilder} as the `responsesBuilder` service.\n *\n * @example\n *\n * // Register it on the container\n * container.register(responsesBuilderProvider);\n * // Getting access to the service instance\n * const responsesBuilder = container.get<ResponsesBuilder>('responsesBuilder');\n *\n * @group Providers\n * @group Services/ResponsesBuilder\n */\nexport const responsesBuilderProvider = provider((app) => {\n app.set(\n 'responsesBuilder',\n () =>\n new ResponsesBuilder({\n inject: {\n config: app.getConfig(),\n statuses: app.get('statuses'),\n },\n }),\n );\n});\n"],"mappings":";;;AACA,SAAS,gBAA+B;AAuFxC,MAAM,uCAAuC;AAOtC,MAAM,iBAAiB;AAAA,EAY5B,YAAY,EAAE,QAAQ,EAAE,QAAQ,SAAS,EAAE,GAAuC;AARlF,wBAAmB;AAInB,wBAAmB;AAKjB,SAAK,UAAU;AACf,SAAK,YAAY;AAAA,EACnB;AAAA,EAQA,gBAAgB,SAA+C;AAC7D,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,aAAa;AAAA,IACf,IAAI;AACJ,UAAM,SAAS,KAAK,QAAQ,IAAwB,oBAAoB,KAAK;AAC7E,UAAM,YAAY,QACd,8CAA8C,
|
|
1
|
+
{"version":3,"sources":["../../../../src/services/http/responsesBuilder.ts"],"sourcesContent":["import type { Config, Response } from '../../types';\nimport { provider, type Statuses } from '../../utils';\n/**\n * The options to construct a {@link ResponsesBuilder}.\n *\n * @group Services/ResponsesBuilder\n */\nexport type ResponsesBuilderConstructorOptions = {\n /**\n * A dictionary with the dependencies to inject.\n */\n inject: {\n config: Config;\n statuses: Statuses;\n };\n};\n/**\n * The options to build a response for a post message.\n *\n * @group Services/ResponsesBuilder\n */\nexport type HTMLPostMessageResponseOptions = {\n /**\n * The response object generated by the application.\n */\n res: Response;\n /**\n * The title for the HTML.\n */\n title: string;\n /**\n * The contents of the post message.\n */\n message: string;\n /**\n * The status code for the response.\n *\n * @default 200\n */\n status?: number | string;\n /**\n * From which object will the `postMessage` method called from.\n *\n * @default 'window.opener'\n */\n target?: string;\n /**\n * Whether or not to call `window.close` after sending the message.\n *\n * @default true\n */\n close?: boolean;\n /**\n * In case `close` is set to `true`, this option is to specify how many milliseconds\n * should be waited before closing the window.\n *\n * @default 700\n */\n closeDelay?: number;\n};\n/**\n * The options to build a reponse for JSON.\n *\n * @group Services/ResponsesBuilder\n */\nexport type JSONResponseOptions = {\n /**\n * The response object generated by the application.\n */\n res: Response;\n /**\n * The actual data for the response. It will be added in a `data` property.\n */\n data: unknown;\n /**\n * The status code for the response.\n *\n * @default 200\n */\n status?: number | string;\n /**\n * Extra information to include inside the `metadata` object.\n */\n metadata?: object;\n};\n/**\n * The default time to wait before closing a window, in case it's enabled.\n */\nconst DEFAULT_CLOSE_DELAY_FOR_POST_MESSAGE = 700;\n/**\n * A utility service to build responses.\n *\n * @group Services\n * @group Services/ResponsesBuilder\n */\nexport class ResponsesBuilder {\n /**\n * The application configuration, to get the `version` and the `postMessagesPrefix`.\n */\n protected readonly _config: Config;\n /**\n * The uility service to get HTTP status codes.\n */\n protected readonly _statuses: Statuses;\n /**\n * @param options The options to construct the service.\n */\n constructor({ inject: { config, statuses } }: ResponsesBuilderConstructorOptions) {\n this._config = config;\n this._statuses = statuses;\n }\n /**\n * Generates and send an HTML response that emits a post message.\n * The post message will be prefixed with the value of the configuration setting\n * `postMessagesPrefix`.\n *\n * @param options The options to customize how the HTML is generated.\n */\n htmlPostMessage(options: HTMLPostMessageResponseOptions): void {\n const {\n res,\n title,\n message,\n status,\n target = 'window.opener',\n close = true,\n closeDelay = DEFAULT_CLOSE_DELAY_FOR_POST_MESSAGE,\n } = options;\n const prefix = this._config.get<string | undefined>('postMessagesPrefix') ?? '';\n const closeCode = close\n ? `setTimeout(function() { window.close(); }, ${closeDelay});`\n : '';\n\n const html = this._htmlTemplate(\n title,\n `\n (function() {\n if (${target}) {\n ${target}.postMessage('${prefix}${message}', '*');\n ${closeCode}\n }\n })();\n `,\n );\n\n const useStatus =\n typeof status === 'undefined'\n ? (this._statuses('ok') as number)\n : this._normalizeStatus(status);\n\n res.setHeader('Content-Type', 'text/html');\n res.setHeader('Cache-Control', 'no-cache, max-age=0, must-revalidate, no-store');\n res.status(useStatus);\n res.write(html);\n res.end();\n }\n /**\n * Generates and sends a JSON response.\n *\n * @param options The options to customize how the JSON is generated.\n * @example\n *\n * <caption>The generated looks like this.</caption>\n *\n * {\n * metadata: {\n * version: 'dev',\n * status: 200,\n * },\n * data: { foo: 'bar' },\n * }\n *\n */\n json(options: JSONResponseOptions): void {\n const { res, data, status, metadata = {} } = options;\n const useStatus =\n typeof status === 'undefined'\n ? (this._statuses('ok') as number)\n : this._normalizeStatus(status);\n\n res.status(useStatus);\n res.json({\n metadata: {\n version: this._config.get<string>('version'),\n status: useStatus,\n ...metadata,\n },\n data,\n });\n res.end();\n }\n /**\n * Generates a basic HTML template for the service to use when generating a post message\n * response.\n *\n * @param title The HTML `<title />` attribute.\n * @param code Javascript code to be wrapped on a `<script />` tag.\n */\n protected _htmlTemplate(title: string, code: string): string {\n return `\n <!DOCTYPE html>\n <html>\n <head>\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${title}</title>\n </head>\n <body>\n <script type=\"text/javascript\">\n ${code}\n </script>\n </body>\n </html>\n `;\n }\n /**\n * Utility method used to make sure a recevied status is a valid status code. If the\n * status is a string, the method will try to find the code from the `statuses` package.\n *\n * @param status The status to normalize.\n * @returns If `status` is a string, but there's no valid code, it will return 200.\n */\n protected _normalizeStatus(status: number | string): number {\n let useStatus: number;\n try {\n if (typeof status === 'string') {\n useStatus = this._statuses(status) as number;\n } else {\n this._statuses(status);\n useStatus = status;\n }\n } catch (_) {\n useStatus = this._statuses('ok') as number;\n }\n\n return useStatus;\n }\n}\n/**\n * The service provider that once registered on the container will set an instance of\n * {@link ResponsesBuilder} as the `responsesBuilder` service.\n *\n * @example\n *\n * // Register it on the container\n * container.register(responsesBuilderProvider);\n * // Getting access to the service instance\n * const responsesBuilder = container.get<ResponsesBuilder>('responsesBuilder');\n *\n * @group Providers\n * @group Services/ResponsesBuilder\n */\nexport const responsesBuilderProvider = provider((app) => {\n app.set(\n 'responsesBuilder',\n () =>\n new ResponsesBuilder({\n inject: {\n config: app.getConfig(),\n statuses: app.get('statuses'),\n },\n }),\n );\n});\n"],"mappings":";;;AACA,SAAS,gBAA+B;AAuFxC,MAAM,uCAAuC;AAOtC,MAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA,EAY5B,YAAY,EAAE,QAAQ,EAAE,QAAQ,SAAS,EAAE,GAAuC;AARlF;AAAA;AAAA;AAAA,wBAAmB;AAInB;AAAA;AAAA;AAAA,wBAAmB;AAKjB,SAAK,UAAU;AACf,SAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,gBAAgB,SAA+C;AAC7D,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,aAAa;AAAA,IACf,IAAI;AACJ,UAAM,SAAS,KAAK,QAAQ,IAAwB,oBAAoB,KAAK;AAC7E,UAAM,YAAY,QACd,8CAA8C,UAAU,OACxD;AAEJ,UAAM,OAAO,KAAK;AAAA,MAChB;AAAA,MACA;AAAA;AAAA,cAEQ,MAAM;AAAA,YACR,MAAM,iBAAiB,MAAM,GAAG,OAAO;AAAA,YACvC,SAAS;AAAA;AAAA;AAAA;AAAA,IAIjB;AAEA,UAAM,YACJ,OAAO,WAAW,cACb,KAAK,UAAU,IAAI,IACpB,KAAK,iBAAiB,MAAM;AAElC,QAAI,UAAU,gBAAgB,WAAW;AACzC,QAAI,UAAU,iBAAiB,gDAAgD;AAC/E,QAAI,OAAO,SAAS;AACpB,QAAI,MAAM,IAAI;AACd,QAAI,IAAI;AAAA,EACV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,KAAK,SAAoC;AACvC,UAAM,EAAE,KAAK,MAAM,QAAQ,WAAW,CAAC,EAAE,IAAI;AAC7C,UAAM,YACJ,OAAO,WAAW,cACb,KAAK,UAAU,IAAI,IACpB,KAAK,iBAAiB,MAAM;AAElC,QAAI,OAAO,SAAS;AACpB,QAAI,KAAK;AAAA,MACP,UAAU;AAAA,QACR,SAAS,KAAK,QAAQ,IAAY,SAAS;AAAA,QAC3C,QAAQ;AAAA,QACR,GAAG;AAAA,MACL;AAAA,MACA;AAAA,IACF,CAAC;AACD,QAAI,IAAI;AAAA,EACV;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,cAAc,OAAe,MAAsB;AAC3D,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAOQ,KAAK;AAAA;AAAA;AAAA;AAAA,YAIZ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,EAKd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQU,iBAAiB,QAAiC;AAC1D,QAAI;AACJ,QAAI;AACF,UAAI,OAAO,WAAW,UAAU;AAC9B,oBAAY,KAAK,UAAU,MAAM;AAAA,MACnC,OAAO;AACL,aAAK,UAAU,MAAM;AACrB,oBAAY;AAAA,MACd;AAAA,IACF,SAAS,GAAG;AACV,kBAAY,KAAK,UAAU,IAAI;AAAA,IACjC;AAEA,WAAO;AAAA,EACT;AACF;AAeO,MAAM,2BAA2B,SAAS,CAAC,QAAQ;AACxD,MAAI;AAAA,IACF;AAAA,IACA,MACE,IAAI,iBAAiB;AAAA,MACnB,QAAQ;AAAA,QACN,QAAQ,IAAI,UAAU;AAAA,QACtB,UAAU,IAAI,IAAI,UAAU;AAAA,MAC9B;AAAA,IACF,CAAC;AAAA,EACL;AACF,CAAC;","names":[]}
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
__publicField
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-2B2CG5KL.js";
|
|
4
4
|
import { deepAssignWithOverwrite } from "@homer0/deep-assign";
|
|
5
5
|
import { providerCreator } from "../../utils";
|
|
6
6
|
class EnsureBearerToken {
|
|
7
|
+
/**
|
|
8
|
+
* @param options The options to construct the class.
|
|
9
|
+
*/
|
|
7
10
|
constructor({
|
|
8
11
|
inject: { HTTPError, statuses },
|
|
9
12
|
...options
|
|
10
13
|
}) {
|
|
14
|
+
/**
|
|
15
|
+
* To generate the errors when the validation fails.
|
|
16
|
+
*/
|
|
11
17
|
__publicField(this, "_HTTPError");
|
|
18
|
+
/**
|
|
19
|
+
* The customization options for the service.
|
|
20
|
+
*/
|
|
12
21
|
__publicField(this, "_options");
|
|
13
22
|
this._HTTPError = HTTPError;
|
|
14
23
|
this._options = deepAssignWithOverwrite(
|
|
@@ -24,6 +33,10 @@ class EnsureBearerToken {
|
|
|
24
33
|
options
|
|
25
34
|
);
|
|
26
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Generates the middleware that verifies if a request has an `Authorization` header
|
|
38
|
+
* with a bearer token.
|
|
39
|
+
*/
|
|
27
40
|
getMiddleware() {
|
|
28
41
|
return (req, res, next) => {
|
|
29
42
|
let unauthorized = true;
|
|
@@ -52,6 +65,9 @@ class EnsureBearerToken {
|
|
|
52
65
|
}
|
|
53
66
|
};
|
|
54
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* The customization options.
|
|
70
|
+
*/
|
|
55
71
|
get options() {
|
|
56
72
|
return deepAssignWithOverwrite({}, this._options);
|
|
57
73
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/services/utils/ensureBearerToken.ts"],"sourcesContent":["import { deepAssignWithOverwrite } from '@homer0/deep-assign';\nimport { providerCreator, type Statuses } from '../../utils';\nimport type { HTTPErrorClass } from '../common';\nimport type { DeepPartial, ExpressMiddleware } from '../../types';\n/**\n * The options for the error the middleare can generate.\n *\n * @group Services/EnsureBearerToken\n */\nexport type EnsureBearerTokenErrorOptions = {\n /**\n * The error message for the response.\n *\n * @default 'Unauthorized'\n */\n message: string;\n /**\n * The HTTP status that will be added to the error context.\n *\n * @default 401\n */\n status: number;\n /**\n * Context information for the error handler and that can be added to the actual\n * response.\n */\n response: unknown;\n};\n/**\n * The options to customize the service/middleware.\n *\n * @group Services/EnsureBearerToken\n */\nexport type EnsureBearerTokenOptions = {\n error: EnsureBearerTokenErrorOptions;\n expression: RegExp;\n local: string;\n};\n/**\n * The options to construct a {@link EnsureBearerToken}.\n *\n * @group Services/EnsureBearerToken\n */\nexport type EnsureBearerConstructorOptions = DeepPartial<EnsureBearerTokenOptions> & {\n /**\n * A dictionary with the dependencies to inject.\n */\n inject: {\n HTTPError: HTTPErrorClass;\n statuses: Statuses;\n };\n};\n/**\n * Custom options for the provider that will register an instance of\n * {@link EnsureBearerToken}\n * as a service.\n *\n * @group Services/EnsureBearerToken\n */\nexport type EnsureBearerTokenProviderOptions = DeepPartial<EnsureBearerTokenOptions> & {\n /**\n * The name that will be used to register the service on the container. This is to allow\n * multiple \"instances\" of the service to be created.\n *\n * @default 'ensureBearerToken'\n */\n serviceName?: string;\n};\n/**\n * This service gives you a middleware that verifies if a request has an `Authorization`\n * header with a bearer token; if it does, the token will be saved on the `res.locals`,\n * otherwise, it will generate an error.\n *\n * This is a \"service middleware\" to allow certain flexibility: you can have controllers\n * where some routes are protected and others are not. For those cases, you get the\n * service from the container, and include it only in the routes that need it.\n *\n * @group Services\n * @group Services/EnsureBearerToken\n */\nexport class EnsureBearerToken {\n /**\n * To generate the errors when the validation fails.\n */\n protected readonly _HTTPError: HTTPErrorClass;\n /**\n * The customization options for the service.\n */\n protected readonly _options: EnsureBearerTokenOptions;\n /**\n * @param options The options to construct the class.\n */\n constructor({\n inject: { HTTPError, statuses },\n ...options\n }: EnsureBearerConstructorOptions) {\n this._HTTPError = HTTPError;\n this._options = deepAssignWithOverwrite(\n {\n error: {\n message: 'Unauthorized',\n status: statuses('unauthorized'),\n response: {},\n },\n expression: /bearer (.*?)(?:$|\\s)/i,\n local: 'token',\n },\n options,\n );\n }\n /**\n * Generates the middleware that verifies if a request has an `Authorization` header\n * with a bearer token.\n */\n getMiddleware(): ExpressMiddleware {\n return (req, res, next) => {\n let unauthorized = true;\n const {\n headers: { authorization },\n } = req;\n if (authorization) {\n const matches = this._options.expression.exec(authorization);\n if (matches) {\n const [, token] = matches;\n res.locals[this._options.local] = token;\n unauthorized = false;\n }\n }\n\n if (unauthorized) {\n const {\n error: { message, status, response },\n } = this._options;\n next(\n new this._HTTPError(message, status, {\n response,\n }),\n );\n } else {\n next();\n }\n };\n }\n /**\n * The customization options.\n */\n get options(): Readonly<EnsureBearerTokenOptions> {\n return deepAssignWithOverwrite({}, this._options);\n }\n}\n/**\n * Generates a \"service middleware\" that can be used on route controllers in order to\n * validate the presence of a bearer token on the requests authorization header.\n *\n * The registered service is an instance of {@link EnsureBearerToken}, and it uses the key\n * `ensureBearerToken`.\n *\n * Since it's a \"provider creator\", when registering it, you can pass custom options.\n *\n * @example\n *\n * <caption>Basic usage</caption>\n *\n * // Register it on the container\n * container.register(ensureBearerTokenProvider);\n *\n * // Let's assume we are in a controller now...\n * // Getting access to the middleware.\n * const ensureBearerToken = container.get<ExpressMiddleware>('ensureBearerToken');\n *\n * @example\n *\n * <caption>Customizing the service</caption>\n *\n * // Register it on the container\n * container.register(\n * ensureBearerTokenProvider({\n * serviceName: 'ensureBearerTokenCustom',\n * error: {\n * message: 'Missing token!',\n * },\n * }),\n * );\n *\n * @group Providers\n * @group Services/EnsureBearerToken\n */\nexport const ensureBearerTokenProvider = providerCreator(\n (options: EnsureBearerTokenProviderOptions = {}) =>\n (app) => {\n const { serviceName = 'ensureBearerToken', ...rest } = options;\n app.set(serviceName, () =>\n new EnsureBearerToken({\n inject: {\n HTTPError: app.get('HTTPError'),\n statuses: app.get('statuses'),\n },\n ...rest,\n }).getMiddleware(),\n );\n },\n);\n"],"mappings":";;;AAAA,SAAS,+BAA+B;AACxC,SAAS,uBAAsC;AA+ExC,MAAM,kBAAkB;AAAA,EAY7B,YAAY;AAAA,IACV,QAAQ,EAAE,WAAW,SAAS;AAAA,
|
|
1
|
+
{"version":3,"sources":["../../../../src/services/utils/ensureBearerToken.ts"],"sourcesContent":["import { deepAssignWithOverwrite } from '@homer0/deep-assign';\nimport { providerCreator, type Statuses } from '../../utils';\nimport type { HTTPErrorClass } from '../common';\nimport type { DeepPartial, ExpressMiddleware } from '../../types';\n/**\n * The options for the error the middleare can generate.\n *\n * @group Services/EnsureBearerToken\n */\nexport type EnsureBearerTokenErrorOptions = {\n /**\n * The error message for the response.\n *\n * @default 'Unauthorized'\n */\n message: string;\n /**\n * The HTTP status that will be added to the error context.\n *\n * @default 401\n */\n status: number;\n /**\n * Context information for the error handler and that can be added to the actual\n * response.\n */\n response: unknown;\n};\n/**\n * The options to customize the service/middleware.\n *\n * @group Services/EnsureBearerToken\n */\nexport type EnsureBearerTokenOptions = {\n error: EnsureBearerTokenErrorOptions;\n expression: RegExp;\n local: string;\n};\n/**\n * The options to construct a {@link EnsureBearerToken}.\n *\n * @group Services/EnsureBearerToken\n */\nexport type EnsureBearerConstructorOptions = DeepPartial<EnsureBearerTokenOptions> & {\n /**\n * A dictionary with the dependencies to inject.\n */\n inject: {\n HTTPError: HTTPErrorClass;\n statuses: Statuses;\n };\n};\n/**\n * Custom options for the provider that will register an instance of\n * {@link EnsureBearerToken}\n * as a service.\n *\n * @group Services/EnsureBearerToken\n */\nexport type EnsureBearerTokenProviderOptions = DeepPartial<EnsureBearerTokenOptions> & {\n /**\n * The name that will be used to register the service on the container. This is to allow\n * multiple \"instances\" of the service to be created.\n *\n * @default 'ensureBearerToken'\n */\n serviceName?: string;\n};\n/**\n * This service gives you a middleware that verifies if a request has an `Authorization`\n * header with a bearer token; if it does, the token will be saved on the `res.locals`,\n * otherwise, it will generate an error.\n *\n * This is a \"service middleware\" to allow certain flexibility: you can have controllers\n * where some routes are protected and others are not. For those cases, you get the\n * service from the container, and include it only in the routes that need it.\n *\n * @group Services\n * @group Services/EnsureBearerToken\n */\nexport class EnsureBearerToken {\n /**\n * To generate the errors when the validation fails.\n */\n protected readonly _HTTPError: HTTPErrorClass;\n /**\n * The customization options for the service.\n */\n protected readonly _options: EnsureBearerTokenOptions;\n /**\n * @param options The options to construct the class.\n */\n constructor({\n inject: { HTTPError, statuses },\n ...options\n }: EnsureBearerConstructorOptions) {\n this._HTTPError = HTTPError;\n this._options = deepAssignWithOverwrite(\n {\n error: {\n message: 'Unauthorized',\n status: statuses('unauthorized'),\n response: {},\n },\n expression: /bearer (.*?)(?:$|\\s)/i,\n local: 'token',\n },\n options,\n );\n }\n /**\n * Generates the middleware that verifies if a request has an `Authorization` header\n * with a bearer token.\n */\n getMiddleware(): ExpressMiddleware {\n return (req, res, next) => {\n let unauthorized = true;\n const {\n headers: { authorization },\n } = req;\n if (authorization) {\n const matches = this._options.expression.exec(authorization);\n if (matches) {\n const [, token] = matches;\n res.locals[this._options.local] = token;\n unauthorized = false;\n }\n }\n\n if (unauthorized) {\n const {\n error: { message, status, response },\n } = this._options;\n next(\n new this._HTTPError(message, status, {\n response,\n }),\n );\n } else {\n next();\n }\n };\n }\n /**\n * The customization options.\n */\n get options(): Readonly<EnsureBearerTokenOptions> {\n return deepAssignWithOverwrite({}, this._options);\n }\n}\n/**\n * Generates a \"service middleware\" that can be used on route controllers in order to\n * validate the presence of a bearer token on the requests authorization header.\n *\n * The registered service is an instance of {@link EnsureBearerToken}, and it uses the key\n * `ensureBearerToken`.\n *\n * Since it's a \"provider creator\", when registering it, you can pass custom options.\n *\n * @example\n *\n * <caption>Basic usage</caption>\n *\n * // Register it on the container\n * container.register(ensureBearerTokenProvider);\n *\n * // Let's assume we are in a controller now...\n * // Getting access to the middleware.\n * const ensureBearerToken = container.get<ExpressMiddleware>('ensureBearerToken');\n *\n * @example\n *\n * <caption>Customizing the service</caption>\n *\n * // Register it on the container\n * container.register(\n * ensureBearerTokenProvider({\n * serviceName: 'ensureBearerTokenCustom',\n * error: {\n * message: 'Missing token!',\n * },\n * }),\n * );\n *\n * @group Providers\n * @group Services/EnsureBearerToken\n */\nexport const ensureBearerTokenProvider = providerCreator(\n (options: EnsureBearerTokenProviderOptions = {}) =>\n (app) => {\n const { serviceName = 'ensureBearerToken', ...rest } = options;\n app.set(serviceName, () =>\n new EnsureBearerToken({\n inject: {\n HTTPError: app.get('HTTPError'),\n statuses: app.get('statuses'),\n },\n ...rest,\n }).getMiddleware(),\n );\n },\n);\n"],"mappings":";;;AAAA,SAAS,+BAA+B;AACxC,SAAS,uBAAsC;AA+ExC,MAAM,kBAAkB;AAAA;AAAA;AAAA;AAAA,EAY7B,YAAY;AAAA,IACV,QAAQ,EAAE,WAAW,SAAS;AAAA,IAC9B,GAAG;AAAA,EACL,GAAmC;AAXnC;AAAA;AAAA;AAAA,wBAAmB;AAInB;AAAA;AAAA;AAAA,wBAAmB;AAQjB,SAAK,aAAa;AAClB,SAAK,WAAW;AAAA,MACd;AAAA,QACE,OAAO;AAAA,UACL,SAAS;AAAA,UACT,QAAQ,SAAS,cAAc;AAAA,UAC/B,UAAU,CAAC;AAAA,QACb;AAAA,QACA,YAAY;AAAA,QACZ,OAAO;AAAA,MACT;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAmC;AACjC,WAAO,CAAC,KAAK,KAAK,SAAS;AACzB,UAAI,eAAe;AACnB,YAAM;AAAA,QACJ,SAAS,EAAE,cAAc;AAAA,MAC3B,IAAI;AACJ,UAAI,eAAe;AACjB,cAAM,UAAU,KAAK,SAAS,WAAW,KAAK,aAAa;AAC3D,YAAI,SAAS;AACX,gBAAM,CAAC,EAAE,KAAK,IAAI;AAClB,cAAI,OAAO,KAAK,SAAS,KAAK,IAAI;AAClC,yBAAe;AAAA,QACjB;AAAA,MACF;AAEA,UAAI,cAAc;AAChB,cAAM;AAAA,UACJ,OAAO,EAAE,SAAS,QAAQ,SAAS;AAAA,QACrC,IAAI,KAAK;AACT;AAAA,UACE,IAAI,KAAK,WAAW,SAAS,QAAQ;AAAA,YACnC;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF,OAAO;AACL,aAAK;AAAA,MACP;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAIA,IAAI,UAA8C;AAChD,WAAO,wBAAwB,CAAC,GAAG,KAAK,QAAQ;AAAA,EAClD;AACF;AAsCO,MAAM,4BAA4B;AAAA,EACvC,CAAC,UAA4C,CAAC,MAC5C,CAAC,QAAQ;AACP,UAAM,EAAE,cAAc,qBAAqB,GAAG,KAAK,IAAI;AACvD,QAAI;AAAA,MAAI;AAAA,MAAa,MACnB,IAAI,kBAAkB;AAAA,QACpB,QAAQ;AAAA,UACN,WAAW,IAAI,IAAI,WAAW;AAAA,UAC9B,UAAU,IAAI,IAAI,UAAU;AAAA,QAC9B;AAAA,QACA,GAAG;AAAA,MACL,CAAC,EAAE,cAAc;AAAA,IACnB;AAAA,EACF;AACJ;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/utils/fns/routes.ts"],"sourcesContent":["import { escapeForRegExp } from './text';\n\n/**\n * Removes any leading slash from a URL.\n *\n * @param url The URL to format.\n * @group Utilities\n */\nexport const removeLeadingSlash = (url: string): string => url.replace(/^\\/+/, '');\n/**\n * Removes any trailing slash from a URL.\n *\n * @param url The URL to format.\n * @group Utilities\n */\nexport const removeTrailingSlash = (url: string): string => url.replace(/\\/+$/, '');\n/**\n * Remove any leading and trailing slash from a URL.\n *\n * @param url The URL to format.\n * @param leading Whether or not to remove any leading slash.\n * @param trailing Whether or not to remove the trailing slash.\n * @group Utilities\n */\nexport const removeSlashes = (\n url: string,\n leading: boolean = true,\n trailing: boolean = true,\n) => {\n const newUrl = leading ? removeLeadingSlash(url) : url;\n return trailing ? removeTrailingSlash(newUrl) : newUrl;\n};\n\n/**\n * Given a server route definition, this function creates a regular expression to match\n * it: The expression replaces the routes parameters with placeholders so it can be\n * compared with real routes.\n *\n * @param route The route from which the expression will be created.\n * @param leadingSlash Whether or not the expression should match a leading slash.\n * @param trailingSlash Whether or not the expression should match a trailing slash.\n * The reason this is `false` by default is because these\n * expressions are often used to match against incoming requests,\n * and they don't have a trailing slash.\n * @group Utilities\n */\nexport const createRouteExpression = (\n route: string,\n leadingSlash: boolean = true,\n trailingSlash: boolean = false,\n): RegExp => {\n let expression = removeSlashes(route)\n .split('/')\n .map((part) => (part.startsWith(':') ? '(?:([^\\\\/]+?))' : escapeForRegExp(part)))\n .join('\\\\/');\n if (leadingSlash) {\n expression = `\\\\/${expression}`;\n }\n if (trailingSlash) {\n expression = `${expression}\\\\/`;\n }\n\n return new RegExp(expression);\n};\n"],"mappings":";AAAA,SAAS,uBAAuB;AAQzB,MAAM,qBAAqB,CAAC,QAAwB,IAAI,QAAQ,QAAQ,EAAE;AAO1E,MAAM,sBAAsB,CAAC,QAAwB,IAAI,QAAQ,QAAQ,EAAE;AAS3E,MAAM,gBAAgB,CAC3B,KACA,UAAmB,MACnB,WAAoB,SACjB;AACH,QAAM,SAAS,UAAU,mBAAmB,GAAG,IAAI;AACnD,SAAO,WAAW,oBAAoB,MAAM,IAAI;AAClD;AAeO,MAAM,wBAAwB,CACnC,OACA,eAAwB,MACxB,gBAAyB,UACd;AACX,MAAI,aAAa,cAAc,KAAK,EACjC,MAAM,GAAG,EACT,IAAI,CAAC,SAAU,KAAK,WAAW,GAAG,IAAI,mBAAmB,gBAAgB,IAAI,CAAE,EAC/E,KAAK,KAAK;AACb,MAAI,cAAc;AAChB,iBAAa,MAAM;AAAA,
|
|
1
|
+
{"version":3,"sources":["../../../../src/utils/fns/routes.ts"],"sourcesContent":["import { escapeForRegExp } from './text';\n\n/**\n * Removes any leading slash from a URL.\n *\n * @param url The URL to format.\n * @group Utilities\n */\nexport const removeLeadingSlash = (url: string): string => url.replace(/^\\/+/, '');\n/**\n * Removes any trailing slash from a URL.\n *\n * @param url The URL to format.\n * @group Utilities\n */\nexport const removeTrailingSlash = (url: string): string => url.replace(/\\/+$/, '');\n/**\n * Remove any leading and trailing slash from a URL.\n *\n * @param url The URL to format.\n * @param leading Whether or not to remove any leading slash.\n * @param trailing Whether or not to remove the trailing slash.\n * @group Utilities\n */\nexport const removeSlashes = (\n url: string,\n leading: boolean = true,\n trailing: boolean = true,\n) => {\n const newUrl = leading ? removeLeadingSlash(url) : url;\n return trailing ? removeTrailingSlash(newUrl) : newUrl;\n};\n\n/**\n * Given a server route definition, this function creates a regular expression to match\n * it: The expression replaces the routes parameters with placeholders so it can be\n * compared with real routes.\n *\n * @param route The route from which the expression will be created.\n * @param leadingSlash Whether or not the expression should match a leading slash.\n * @param trailingSlash Whether or not the expression should match a trailing slash.\n * The reason this is `false` by default is because these\n * expressions are often used to match against incoming requests,\n * and they don't have a trailing slash.\n * @group Utilities\n */\nexport const createRouteExpression = (\n route: string,\n leadingSlash: boolean = true,\n trailingSlash: boolean = false,\n): RegExp => {\n let expression = removeSlashes(route)\n .split('/')\n .map((part) => (part.startsWith(':') ? '(?:([^\\\\/]+?))' : escapeForRegExp(part)))\n .join('\\\\/');\n if (leadingSlash) {\n expression = `\\\\/${expression}`;\n }\n if (trailingSlash) {\n expression = `${expression}\\\\/`;\n }\n\n return new RegExp(expression);\n};\n"],"mappings":";AAAA,SAAS,uBAAuB;AAQzB,MAAM,qBAAqB,CAAC,QAAwB,IAAI,QAAQ,QAAQ,EAAE;AAO1E,MAAM,sBAAsB,CAAC,QAAwB,IAAI,QAAQ,QAAQ,EAAE;AAS3E,MAAM,gBAAgB,CAC3B,KACA,UAAmB,MACnB,WAAoB,SACjB;AACH,QAAM,SAAS,UAAU,mBAAmB,GAAG,IAAI;AACnD,SAAO,WAAW,oBAAoB,MAAM,IAAI;AAClD;AAeO,MAAM,wBAAwB,CACnC,OACA,eAAwB,MACxB,gBAAyB,UACd;AACX,MAAI,aAAa,cAAc,KAAK,EACjC,MAAM,GAAG,EACT,IAAI,CAAC,SAAU,KAAK,WAAW,GAAG,IAAI,mBAAmB,gBAAgB,IAAI,CAAE,EAC/E,KAAK,KAAK;AACb,MAAI,cAAc;AAChB,iBAAa,MAAM,UAAU;AAAA,EAC/B;AACA,MAAI,eAAe;AACjB,iBAAa,GAAG,UAAU;AAAA,EAC5B;AAEA,SAAO,IAAI,OAAO,UAAU;AAC9B;","names":[]}
|