Skip to content

Commit

Permalink
fix(next-app-router): escape injected results for hydration (#6141)
Browse files Browse the repository at this point in the history
* fix(next-app-router): escape injected results for hydration

* add tests

* exclude tests from types build
  • Loading branch information
aymeric-giraudet committed Apr 17, 2024
1 parent 1b683c5 commit 2e6f0de
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 4 deletions.
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const config = {
'<rootDir>/packages/algoliasearch-helper',
'<rootDir>/packages/create-instantsearch-app',
'<rootDir>/packages/react-instantsearch-router-nextjs/__tests__',
'<rootDir>/packages/react-instantsearch-nextjs',
'<rootDir>/packages/react-instantsearch-nextjs/__tests__',
'/__utils__/',
],
watchPathIgnorePatterns: [
Expand Down
6 changes: 4 additions & 2 deletions packages/react-instantsearch-nextjs/src/InitializePromise.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
wrapPromiseWithState,
} from 'react-instantsearch-core';

import { htmlEscapeJsonString } from './htmlEscape';

import type { SearchOptions } from 'instantsearch.js';

export function InitializePromise() {
Expand Down Expand Up @@ -47,8 +49,8 @@ export function InitializePromise() {
return (
<script
dangerouslySetInnerHTML={{
__html: `window[Symbol.for("InstantSearchInitialResults")] = ${JSON.stringify(
results
__html: `window[Symbol.for("InstantSearchInitialResults")] = ${htmlEscapeJsonString(
JSON.stringify(results)
)}`,
}}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { htmlEscapeJsonString } from '../htmlEscape';

it('encodes HTML related characters into entities that are decoded by JSON.parse', () => {
const input = { description: '<h1>Hello</h1>' };
const output = htmlEscapeJsonString(JSON.stringify(input));

expect(output).toBe(
'{"description":"\\u003ch1\\u003eHello\\u003c/h1\\u003e"}'
);
expect(JSON.parse(output)).toEqual(input);
});
19 changes: 19 additions & 0 deletions packages/react-instantsearch-nextjs/src/htmlEscape.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// This file was taken from the Next.js repo : https://github.com/vercel/next.js/blob/754fadacf30c145009506662bfbd2a4ccebb377d/packages/next/src/server/htmlescape.ts
// License: https://github.com/vercel/next.js/blob/754fadacf30c145009506662bfbd2a4ccebb377d/license.md

// This utility is based on https://github.com/zertosh/htmlescape
// License: https://github.com/zertosh/htmlescape/blob/0527ca7156a524d256101bb310a9f970f63078ad/LICENSE

const ESCAPE_LOOKUP: { [match: string]: string } = {
'&': '\\u0026',
'>': '\\u003e',
'<': '\\u003c',
'\u2028': '\\u2028',
'\u2029': '\\u2029',
};

export const ESCAPE_REGEX = /[&><\u2028\u2029]/g;

export function htmlEscapeJsonString(str: string): string {
return str.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"extends": "../../tsconfig.declaration",
"exclude": ["**/__tests__/e2e/**/*", "**/dist/**/*"]
"exclude": ["**/__tests__/**/*", "**/dist/**/*"]
}

0 comments on commit 2e6f0de

Please sign in to comment.