Skip to content

Commit f31bef1

Browse files
authored
Enhancement(Share): Enhancement base64 encoding/decoding using UTF-8 charset to support the characters outside the latin1 range. (#4024) (#4034)
* Enhancement(Share): Declare and export an object that provides base64 encoding and decoding functions using the utf-8 charset to support the characters outside the latin1 range. (#4024) * Enhancement(Share): Add the 'base64.test.ts' to test the base64. (#4024) * Enhancement(Share): Update the base64 reference in 'Playground' to the new customized base64 in 'utils' (#4024). * Enhancement(Share): Update 'CHANGELOG.md' (#4024). * Enhancement(Share): Update 'CHANGELOG.md' (#4024). * Enhancement(Share): Add test to test the platform behavior (#4024). * Enhancement: Fix comments and updating utility-functions.md to add introduction of base64 object * Update CHANGELOG.md * Update CHANGELOG.md * Update base64.ts
1 parent 4f8d080 commit f31bef1

File tree

7 files changed

+141
-4
lines changed

7 files changed

+141
-4
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ should change the heading of the (upcoming) version to include a major version b
1616
1717
-->
1818

19+
# 5.16.2
20+
21+
## @rjsf/utils
22+
23+
- [4024](https://github.com/rjsf-team/react-jsonschema-form/issues/4024) Added `base64` to support `encoding`
24+
and `decoding` using the `UTF-8` charset to support the characters out of the `Latin1` range.
25+
26+
## Dev / docs / playground
27+
28+
- [4024](https://github.com/rjsf-team/react-jsonschema-form/issues/4024) Updated the base64 references from (`atob`
29+
and `btoa`) to invoke the functions from the new `base64` object in `@rjsf/utils`.
30+
1931
# 5.16.1
2032

2133
## Dev / docs / playground

packages/docs/docs/api-reference/utility-functions.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,3 +1181,34 @@ For more information about how to specify the path see the [eslint lodash plugin
11811181
#### Returns
11821182

11831183
- ErrorSchemaBuilder<T> - The instance of the `ErrorSchemaBuilder` class
1184+
1185+
## utility object
1186+
1187+
### base64
1188+
1189+
An object providing base64 encoding and decoding functions using the UTF-8 charset.
1190+
By default, the `btoa()` and `atob()` built-in functions are only support the Latin-1 character range that means that non Latin-1 characters are not supported(for example, Chinese characters).
1191+
1192+
#### encode()
1193+
1194+
Encodes the given `input` string into a base64 encoded string
1195+
1196+
##### Parameters
1197+
1198+
- input: string - The string to encode
1199+
1200+
##### Returns
1201+
1202+
- string: The base64 encoded string
1203+
1204+
#### decode()
1205+
1206+
Decodes the given `input` string from a base64 encoded string
1207+
1208+
##### Parameters
1209+
1210+
- input: string - The string to decode
1211+
1212+
##### Returns
1213+
1214+
- string: The decoded string

packages/playground/src/components/Header.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useCallback } from 'react';
22
import Form, { IChangeEvent } from '@rjsf/core';
3-
import { RJSFSchema, UiSchema, ValidatorType } from '@rjsf/utils';
3+
import { base64, RJSFSchema, UiSchema, ValidatorType } from '@rjsf/utils';
44
import localValidator from '@rjsf/validator-ajv8';
55

66
import CopyLink from './CopyLink';
@@ -245,7 +245,7 @@ export default function Header({
245245
} = document;
246246

247247
try {
248-
const hash = btoa(
248+
const hash = base64.encode(
249249
JSON.stringify({
250250
formData,
251251
schema,

packages/playground/src/components/Playground.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ComponentType, FormEvent, useCallback, useEffect, useRef, useState } from 'react';
22
import { FormProps, IChangeEvent, withTheme } from '@rjsf/core';
3-
import { ErrorSchema, RJSFSchema, RJSFValidationError, UiSchema, ValidatorType } from '@rjsf/utils';
3+
import { base64, ErrorSchema, RJSFSchema, RJSFValidationError, UiSchema, ValidatorType } from '@rjsf/utils';
44

55
import { samples } from '../samples';
66
import Header, { LiveSettings } from './Header';
@@ -92,7 +92,8 @@ export default function Playground({ themes, validators }: PlaygroundProps) {
9292

9393
if (hash && typeof hash[1] === 'string' && hash[1].length > 0 && !loaded) {
9494
try {
95-
load(JSON.parse(atob(hash[1])));
95+
const decoded = base64.decode(hash[1]);
96+
load(JSON.parse(decoded));
9697
setLoaded(true);
9798
} catch (error) {
9899
alert('Unable to load form setup data.');

packages/utils/src/base64.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* An object that provides base64 encoding and decoding functions using the utf-8 charset to support the characters outside the latin1 range
3+
* By default, btoa() and atob() only support the latin1 character range.
4+
*/
5+
const base64 = (function () {
6+
// If we are in the browser, we can use the built-in TextEncoder and TextDecoder
7+
// Otherwise, it is assumed that we are in node.js, and we can use the util module's TextEncoder and TextDecoder
8+
return {
9+
encode(text: string): string {
10+
let encoder: any;
11+
if (typeof TextEncoder !== 'undefined') {
12+
encoder = new TextEncoder();
13+
} else {
14+
const { TextEncoder } = require('util');
15+
encoder = new TextEncoder();
16+
}
17+
return btoa(String.fromCharCode(...encoder.encode(text)));
18+
},
19+
decode(text: string): string {
20+
let decoder: any;
21+
if (typeof TextDecoder !== 'undefined') {
22+
decoder = new TextDecoder();
23+
} else {
24+
const { TextDecoder } = require('util');
25+
decoder = new TextDecoder();
26+
}
27+
return decoder.decode(Uint8Array.from(atob(text), (c) => c.charCodeAt(0)));
28+
},
29+
};
30+
})();
31+
32+
export default base64;

packages/utils/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import utcToLocal from './utcToLocal';
5151
import validationDataMerge from './validationDataMerge';
5252
import withIdRefPrefix from './withIdRefPrefix';
5353
import getOptionMatchingSimpleDiscriminator from './getOptionMatchingSimpleDiscriminator';
54+
import base64 from './base64';
5455

5556
export * from './types';
5657
export * from './enums';
@@ -120,4 +121,5 @@ export {
120121
utcToLocal,
121122
validationDataMerge,
122123
withIdRefPrefix,
124+
base64,
123125
};

packages/utils/test/base64.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { base64 } from '../src';
2+
3+
describe('base64', () => {
4+
it('should successfully encode a ascii character', () => {
5+
expect(base64.encode('v')).toEqual('dg==');
6+
});
7+
it('should successfully encode a Chinese character', () => {
8+
expect(base64.encode('我')).toEqual('5oiR');
9+
});
10+
it('should successfully encode ascii characters', () => {
11+
expect(base64.encode('vs')).toEqual('dnM=');
12+
});
13+
it('should successfully encode a Chinese characters', () => {
14+
expect(base64.encode('我是')).toEqual('5oiR5piv');
15+
});
16+
it('should successfully decode a ascii character', () => {
17+
expect(base64.decode('dg==')).toEqual('v');
18+
});
19+
it('should successfully decode a Chinese character', () => {
20+
expect(base64.decode('5oiR')).toEqual('我');
21+
});
22+
it('should successfully decode ascii characters', () => {
23+
expect(base64.decode('dnM=')).toEqual('vs');
24+
});
25+
it('should successfully decode a Chinese characters', () => {
26+
expect(base64.decode('5oiR5piv')).toEqual('我是');
27+
});
28+
});
29+
30+
describe('nodejs behavior', () => {
31+
it('should successfully create a base64 object and encode/decode string in node.js', () => {
32+
expect(base64.encode('我是')).toEqual('5oiR5piv');
33+
expect(base64.decode('5oiR5piv')).toEqual('我是');
34+
});
35+
});
36+
37+
describe('browser behavior', () => {
38+
// capture the TextEncoder and TextDecoder from the util module and assign them to the global object (for mocking browser environment)
39+
beforeAll(() => {
40+
const { TextDecoder } = require('util');
41+
global.TextDecoder = TextDecoder;
42+
43+
const { TextEncoder } = require('util');
44+
global.TextEncoder = TextEncoder;
45+
});
46+
// restore the TextEncoder and TextDecoder to undefined
47+
afterAll(() => {
48+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
49+
// @ts-ignore
50+
global.TextEncoder = undefined;
51+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
52+
// @ts-ignore
53+
global.TextDecoder = undefined;
54+
});
55+
it('should successfully create a base64 object and encode/decode string in browser', () => {
56+
expect(base64.encode('我是')).toEqual('5oiR5piv');
57+
expect(base64.decode('5oiR5piv')).toEqual('我是');
58+
});
59+
});

0 commit comments

Comments
 (0)