Skip to content

Commit

Permalink
Enhancement(Share): Enhancement base64 encoding/decoding using UTF-8 …
Browse files Browse the repository at this point in the history
…charset to support the characters outside the latin1 range. (rjsf-team#4024) (rjsf-team#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. (rjsf-team#4024)

* Enhancement(Share): Add the 'base64.test.ts' to test the base64. (rjsf-team#4024)

* Enhancement(Share): Update the base64 reference in 'Playground' to the new customized base64 in 'utils' (rjsf-team#4024).

* Enhancement(Share): Update 'CHANGELOG.md' (rjsf-team#4024).

* Enhancement(Share): Update 'CHANGELOG.md' (rjsf-team#4024).

* Enhancement(Share): Add test to test the platform behavior (rjsf-team#4024).

* Enhancement: Fix comments and updating utility-functions.md to add introduction of base64 object

* Update CHANGELOG.md

* Update CHANGELOG.md

* Update base64.ts
  • Loading branch information
orange-guo authored Jan 18, 2024
1 parent 4f8d080 commit f31bef1
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 4 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ should change the heading of the (upcoming) version to include a major version b
-->

# 5.16.2

## @rjsf/utils

- [4024](https://github.com/rjsf-team/react-jsonschema-form/issues/4024) Added `base64` to support `encoding`
and `decoding` using the `UTF-8` charset to support the characters out of the `Latin1` range.

## Dev / docs / playground

- [4024](https://github.com/rjsf-team/react-jsonschema-form/issues/4024) Updated the base64 references from (`atob`
and `btoa`) to invoke the functions from the new `base64` object in `@rjsf/utils`.

# 5.16.1

## Dev / docs / playground
Expand Down
31 changes: 31 additions & 0 deletions packages/docs/docs/api-reference/utility-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -1181,3 +1181,34 @@ For more information about how to specify the path see the [eslint lodash plugin
#### Returns

- ErrorSchemaBuilder<T> - The instance of the `ErrorSchemaBuilder` class

## utility object

### base64

An object providing base64 encoding and decoding functions using the UTF-8 charset.
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).

#### encode()

Encodes the given `input` string into a base64 encoded string

##### Parameters

- input: string - The string to encode

##### Returns

- string: The base64 encoded string

#### decode()

Decodes the given `input` string from a base64 encoded string

##### Parameters

- input: string - The string to decode

##### Returns

- string: The decoded string
4 changes: 2 additions & 2 deletions packages/playground/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useCallback } from 'react';
import Form, { IChangeEvent } from '@rjsf/core';
import { RJSFSchema, UiSchema, ValidatorType } from '@rjsf/utils';
import { base64, RJSFSchema, UiSchema, ValidatorType } from '@rjsf/utils';
import localValidator from '@rjsf/validator-ajv8';

import CopyLink from './CopyLink';
Expand Down Expand Up @@ -245,7 +245,7 @@ export default function Header({
} = document;

try {
const hash = btoa(
const hash = base64.encode(
JSON.stringify({
formData,
schema,
Expand Down
5 changes: 3 additions & 2 deletions packages/playground/src/components/Playground.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ComponentType, FormEvent, useCallback, useEffect, useRef, useState } from 'react';
import { FormProps, IChangeEvent, withTheme } from '@rjsf/core';
import { ErrorSchema, RJSFSchema, RJSFValidationError, UiSchema, ValidatorType } from '@rjsf/utils';
import { base64, ErrorSchema, RJSFSchema, RJSFValidationError, UiSchema, ValidatorType } from '@rjsf/utils';

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

if (hash && typeof hash[1] === 'string' && hash[1].length > 0 && !loaded) {
try {
load(JSON.parse(atob(hash[1])));
const decoded = base64.decode(hash[1]);
load(JSON.parse(decoded));
setLoaded(true);
} catch (error) {
alert('Unable to load form setup data.');
Expand Down
32 changes: 32 additions & 0 deletions packages/utils/src/base64.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* An object that provides base64 encoding and decoding functions using the utf-8 charset to support the characters outside the latin1 range
* By default, btoa() and atob() only support the latin1 character range.
*/
const base64 = (function () {
// If we are in the browser, we can use the built-in TextEncoder and TextDecoder
// Otherwise, it is assumed that we are in node.js, and we can use the util module's TextEncoder and TextDecoder
return {
encode(text: string): string {
let encoder: any;
if (typeof TextEncoder !== 'undefined') {
encoder = new TextEncoder();
} else {
const { TextEncoder } = require('util');
encoder = new TextEncoder();
}
return btoa(String.fromCharCode(...encoder.encode(text)));
},
decode(text: string): string {
let decoder: any;
if (typeof TextDecoder !== 'undefined') {
decoder = new TextDecoder();
} else {
const { TextDecoder } = require('util');
decoder = new TextDecoder();
}
return decoder.decode(Uint8Array.from(atob(text), (c) => c.charCodeAt(0)));
},
};
})();

export default base64;
2 changes: 2 additions & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import utcToLocal from './utcToLocal';
import validationDataMerge from './validationDataMerge';
import withIdRefPrefix from './withIdRefPrefix';
import getOptionMatchingSimpleDiscriminator from './getOptionMatchingSimpleDiscriminator';
import base64 from './base64';

export * from './types';
export * from './enums';
Expand Down Expand Up @@ -120,4 +121,5 @@ export {
utcToLocal,
validationDataMerge,
withIdRefPrefix,
base64,
};
59 changes: 59 additions & 0 deletions packages/utils/test/base64.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { base64 } from '../src';

describe('base64', () => {
it('should successfully encode a ascii character', () => {
expect(base64.encode('v')).toEqual('dg==');
});
it('should successfully encode a Chinese character', () => {
expect(base64.encode('我')).toEqual('5oiR');
});
it('should successfully encode ascii characters', () => {
expect(base64.encode('vs')).toEqual('dnM=');
});
it('should successfully encode a Chinese characters', () => {
expect(base64.encode('我是')).toEqual('5oiR5piv');
});
it('should successfully decode a ascii character', () => {
expect(base64.decode('dg==')).toEqual('v');
});
it('should successfully decode a Chinese character', () => {
expect(base64.decode('5oiR')).toEqual('我');
});
it('should successfully decode ascii characters', () => {
expect(base64.decode('dnM=')).toEqual('vs');
});
it('should successfully decode a Chinese characters', () => {
expect(base64.decode('5oiR5piv')).toEqual('我是');
});
});

describe('nodejs behavior', () => {
it('should successfully create a base64 object and encode/decode string in node.js', () => {
expect(base64.encode('我是')).toEqual('5oiR5piv');
expect(base64.decode('5oiR5piv')).toEqual('我是');
});
});

describe('browser behavior', () => {
// capture the TextEncoder and TextDecoder from the util module and assign them to the global object (for mocking browser environment)
beforeAll(() => {
const { TextDecoder } = require('util');
global.TextDecoder = TextDecoder;

const { TextEncoder } = require('util');
global.TextEncoder = TextEncoder;
});
// restore the TextEncoder and TextDecoder to undefined
afterAll(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
global.TextEncoder = undefined;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
global.TextDecoder = undefined;
});
it('should successfully create a base64 object and encode/decode string in browser', () => {
expect(base64.encode('我是')).toEqual('5oiR5piv');
expect(base64.decode('5oiR5piv')).toEqual('我是');
});
});

0 comments on commit f31bef1

Please sign in to comment.