diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..00ad71f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules\\typescript\\lib" +} \ No newline at end of file diff --git a/package.json b/package.json index e3f4aaa..e9cfa79 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "string-replace-middleware", - "version": "1.1.0", + "version": "1.2.0", "description": "Express middleware to replace strings in response stream on the fly", "license": "MIT", "repository": "bfncs/string-replace-middleware", @@ -51,4 +51,4 @@ "singleQuote": true, "trailingComma": "es5" } -} +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 2b5705d..0c4c8b7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,14 +1,14 @@ import { NextFunction, Request, Response } from 'express'; import hijackResponse from 'hijackresponse'; -import stringReplaceStream from './stringReplaceStream'; +import stringReplaceStream, { MatchReplacement } from './stringReplaceStream'; -export type Options = Record<'contentTypeFilterRegexp', RegExp>; +export type ReplaceFunction = (req: Request, res: Response) => MatchReplacement; -export type ReplaceFunction = (req: Request, res: Response) => string; - -const defaultOptions: Options = { +const defaultOptions = { contentTypeFilterRegexp: /^text\/|^application\/json$|^application\/xml$/, + useRegExp: false, }; +type Options = typeof defaultOptions; export const stringReplace = ( replacements: Record, @@ -39,7 +39,7 @@ export const stringReplace = ( } res.removeHeader('content-length'); - let scopedReplacements: Record; + let scopedReplacements: Record; if (hasFunctionReplacements) { // If we have dynamic replacements, calculate for this request scopedReplacements = { ...stringReplacements }; @@ -51,7 +51,13 @@ export const stringReplace = ( scopedReplacements = stringReplacements; } - res.pipe(stringReplaceStream(scopedReplacements)).pipe(res); + res + .pipe( + stringReplaceStream(scopedReplacements, { + useRegExp: options.useRegExp, + }) + ) + .pipe(res); } else { return res.unhijack(); } diff --git a/src/stringReplaceStream.ts b/src/stringReplaceStream.ts index d6a3416..83d0e36 100644 --- a/src/stringReplaceStream.ts +++ b/src/stringReplaceStream.ts @@ -1,36 +1,45 @@ import { Transform, TransformCallback } from 'stream'; import escapeStringRegexp from 'escape-string-regexp'; -type Options = { - encoding: BufferEncoding; - ignoreCase: boolean; -}; +/** The replacer string or function passed to string.replace(text: string, replacer: *MatchReplacement*) */ +export type MatchReplacement = + | string + | ((matchedSubstring: string, ...capturedGroups: string[]) => string); + type Replacer = { matcher: RegExp; - replace: string; + replace: MatchReplacement; }; +type Options = { + encoding: BufferEncoding; + ignoreCase: boolean; + useRegExp: boolean; +}; const defaultOptions: Options = { encoding: 'utf8', ignoreCase: true, + useRegExp: false, }; function buildReplacers( - replacements: Record, + replacements: Record, opts: Options ): Replacer[] { return Object.keys(replacements) .sort((a, b) => b.length - a.length) .map(search => ({ matcher: new RegExp( - escapeStringRegexp(search), + opts.useRegExp ? search : escapeStringRegexp(search), opts.ignoreCase ? 'gmi' : 'gm' ), replace: replacements[search], })); } -function getMaxSearchLength(replacements: Record): number { +function getMaxSearchLength( + replacements: Record +): number { return Object.keys(replacements).reduce( (acc, search) => Math.max(acc, search.length), 0 @@ -38,7 +47,7 @@ function getMaxSearchLength(replacements: Record): number { } export default function StringReplaceStream( - replacements: Record, + replacements: Record, options: Partial = {} ) { const opts: Options = { ...defaultOptions, ...options }; @@ -65,7 +74,7 @@ export default function StringReplaceStream( body = body .slice(0, replaceBefore) - .replace(replacer.matcher, replacer.replace) + + .replace(replacer.matcher, replacer.replace as string) + body.slice(replaceBefore); }); @@ -98,7 +107,8 @@ export default function StringReplaceStream( } const body = replacers.reduce( - (acc, replacer) => acc.replace(replacer.matcher, replacer.replace), + (acc, replacer) => + acc.replace(replacer.matcher, replacer.replace as string), tail ); cb(null, body);