mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-09-02 11:42:53 +02:00
implement scrubber for end user data in exceptions (#4999)
This commit is contained in:
committed by
GitHub
parent
25be3b7031
commit
fe13a8f9e7
@@ -1,5 +1,5 @@
|
||||
import React, { useMemo, useState, useCallback, useEffect, useRef } from 'react'
|
||||
import { Editor, Node, Element, Descendant } from 'slate'
|
||||
import React, { useState, useCallback, useEffect, useRef } from 'react'
|
||||
import { Editor, Node, Descendant, Scrubber } from 'slate'
|
||||
import { ReactEditor } from '../plugin/react-editor'
|
||||
import { FocusedContext } from '../hooks/use-focused'
|
||||
import { EditorContext } from '../hooks/use-slate-static'
|
||||
@@ -30,12 +30,13 @@ export const Slate = (props: {
|
||||
if (!Node.isNodeList(value)) {
|
||||
throw new Error(
|
||||
`[Slate] value is invalid! Expected a list of elements` +
|
||||
`but got: ${JSON.stringify(value)}`
|
||||
`but got: ${Scrubber.stringify(value)}`
|
||||
)
|
||||
}
|
||||
if (!Editor.isEditor(editor)) {
|
||||
throw new Error(
|
||||
`[Slate] editor is invalid! you passed:` + `${JSON.stringify(editor)}`
|
||||
`[Slate] editor is invalid! you passed:` +
|
||||
`${Scrubber.stringify(editor)}`
|
||||
)
|
||||
}
|
||||
editor.children = value
|
||||
|
@@ -1,4 +1,13 @@
|
||||
import { Editor, Node, Path, Point, Range, Transforms, BaseEditor } from 'slate'
|
||||
import {
|
||||
BaseEditor,
|
||||
Editor,
|
||||
Node,
|
||||
Path,
|
||||
Point,
|
||||
Range,
|
||||
Scrubber,
|
||||
Transforms,
|
||||
} from 'slate'
|
||||
|
||||
import { Key } from '../utils/key'
|
||||
import {
|
||||
@@ -108,7 +117,7 @@ export const ReactEditor = {
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Unable to find the path for Slate node: ${JSON.stringify(node)}`
|
||||
`Unable to find the path for Slate node: ${Scrubber.stringify(node)}`
|
||||
)
|
||||
},
|
||||
|
||||
@@ -285,7 +294,7 @@ export const ReactEditor = {
|
||||
|
||||
if (!domNode) {
|
||||
throw new Error(
|
||||
`Cannot resolve a DOM node from Slate node: ${JSON.stringify(node)}`
|
||||
`Cannot resolve a DOM node from Slate node: ${Scrubber.stringify(node)}`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -337,7 +346,9 @@ export const ReactEditor = {
|
||||
|
||||
if (!domPoint) {
|
||||
throw new Error(
|
||||
`Cannot resolve a DOM point from Slate point: ${JSON.stringify(point)}`
|
||||
`Cannot resolve a DOM point from Slate point: ${Scrubber.stringify(
|
||||
point
|
||||
)}`
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
export * from './create-editor'
|
||||
export * from './interfaces/custom-types'
|
||||
export * from './interfaces/editor'
|
||||
export * from './interfaces/element'
|
||||
export * from './interfaces/location'
|
||||
@@ -10,6 +11,6 @@ export * from './interfaces/point'
|
||||
export * from './interfaces/point-ref'
|
||||
export * from './interfaces/range'
|
||||
export * from './interfaces/range-ref'
|
||||
export * from './interfaces/scrubber'
|
||||
export * from './interfaces/text'
|
||||
export * from './interfaces/custom-types'
|
||||
export * from './transforms'
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { produce } from 'immer'
|
||||
import { Editor, Path, Range, Text } from '..'
|
||||
import { Editor, Path, Range, Text, Scrubber } from '..'
|
||||
import { Element, ElementEntry } from './element'
|
||||
|
||||
/**
|
||||
@@ -112,7 +112,9 @@ export const Node: NodeInterface = {
|
||||
|
||||
if (Text.isText(node)) {
|
||||
throw new Error(
|
||||
`Cannot get the ancestor node at path [${path}] because it refers to a text node instead: ${node}`
|
||||
`Cannot get the ancestor node at path [${path}] because it refers to a text node instead: ${Scrubber.stringify(
|
||||
node
|
||||
)}`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -145,7 +147,7 @@ export const Node: NodeInterface = {
|
||||
child(root: Node, index: number): Descendant {
|
||||
if (Text.isText(root)) {
|
||||
throw new Error(
|
||||
`Cannot get the child of a text node: ${JSON.stringify(root)}`
|
||||
`Cannot get the child of a text node: ${Scrubber.stringify(root)}`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -153,7 +155,7 @@ export const Node: NodeInterface = {
|
||||
|
||||
if (c == null) {
|
||||
throw new Error(
|
||||
`Cannot get child at index \`${index}\` in node: ${JSON.stringify(
|
||||
`Cannot get child at index \`${index}\` in node: ${Scrubber.stringify(
|
||||
root
|
||||
)}`
|
||||
)
|
||||
@@ -203,7 +205,9 @@ export const Node: NodeInterface = {
|
||||
|
||||
if (Editor.isEditor(node)) {
|
||||
throw new Error(
|
||||
`Cannot get the descendant node at path [${path}] because it refers to the root editor node instead: ${node}`
|
||||
`Cannot get the descendant node at path [${path}] because it refers to the root editor node instead: ${Scrubber.stringify(
|
||||
node
|
||||
)}`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -287,7 +291,7 @@ export const Node: NodeInterface = {
|
||||
fragment(root: Node, range: Range): Descendant[] {
|
||||
if (Text.isText(root)) {
|
||||
throw new Error(
|
||||
`Cannot get a fragment starting from a root text node: ${JSON.stringify(
|
||||
`Cannot get a fragment starting from a root text node: ${Scrubber.stringify(
|
||||
root
|
||||
)}`
|
||||
)
|
||||
@@ -339,7 +343,7 @@ export const Node: NodeInterface = {
|
||||
|
||||
if (Text.isText(node) || !node.children[p]) {
|
||||
throw new Error(
|
||||
`Cannot find a descendant at path [${path}] in node: ${JSON.stringify(
|
||||
`Cannot find a descendant at path [${path}] in node: ${Scrubber.stringify(
|
||||
root
|
||||
)}`
|
||||
)
|
||||
@@ -428,7 +432,9 @@ export const Node: NodeInterface = {
|
||||
|
||||
if (!Text.isText(node)) {
|
||||
throw new Error(
|
||||
`Cannot get the leaf node at path [${path}] because it refers to a non-leaf node: ${node}`
|
||||
`Cannot get the leaf node at path [${path}] because it refers to a non-leaf node: ${Scrubber.stringify(
|
||||
node
|
||||
)}`
|
||||
)
|
||||
}
|
||||
|
||||
|
33
packages/slate/src/interfaces/scrubber.ts
Normal file
33
packages/slate/src/interfaces/scrubber.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export type Scrubber = (key: string, value: unknown) => unknown
|
||||
|
||||
export interface ScrubberInterface {
|
||||
setScrubber(scrubber: Scrubber | undefined): void
|
||||
stringify(value: any): string
|
||||
}
|
||||
|
||||
let _scrubber: Scrubber | undefined = undefined
|
||||
|
||||
/**
|
||||
* This interface implements a stringify() function, which is used by Slate
|
||||
* internally when generating exceptions containing end user data. Developers
|
||||
* using Slate may call Scrubber.setScrubber() to alter the behavior of this
|
||||
* stringify() function.
|
||||
*
|
||||
* For example, to prevent the cleartext logging of 'text' fields within Nodes:
|
||||
*
|
||||
* import { Scrubber } from 'slate';
|
||||
* Scrubber.setScrubber((key, val) => {
|
||||
* if (key === 'text') return '...scrubbed...'
|
||||
* return val
|
||||
* });
|
||||
*
|
||||
*/
|
||||
export const Scrubber: ScrubberInterface = {
|
||||
setScrubber(scrubber: Scrubber | undefined): void {
|
||||
_scrubber = scrubber
|
||||
},
|
||||
|
||||
stringify(value: any): string {
|
||||
return JSON.stringify(value, _scrubber)
|
||||
},
|
||||
}
|
@@ -1,17 +1,18 @@
|
||||
import { createDraft, finishDraft, isDraft } from 'immer'
|
||||
import {
|
||||
Node,
|
||||
Editor,
|
||||
Selection,
|
||||
Range,
|
||||
Point,
|
||||
Text,
|
||||
Element,
|
||||
Operation,
|
||||
Descendant,
|
||||
NodeEntry,
|
||||
Path,
|
||||
Ancestor,
|
||||
Descendant,
|
||||
Editor,
|
||||
Element,
|
||||
Node,
|
||||
NodeEntry,
|
||||
Operation,
|
||||
Path,
|
||||
Point,
|
||||
Range,
|
||||
Scrubber,
|
||||
Selection,
|
||||
Text,
|
||||
} from '..'
|
||||
|
||||
export interface GeneralTransforms {
|
||||
@@ -73,7 +74,9 @@ const applyToDraft = (editor: Editor, selection: Selection, op: Operation) => {
|
||||
prev.children.push(...node.children)
|
||||
} else {
|
||||
throw new Error(
|
||||
`Cannot apply a "merge_node" operation at path [${path}] to nodes of different interfaces: ${node} ${prev}`
|
||||
`Cannot apply a "merge_node" operation at path [${path}] to nodes of different interfaces: ${Scrubber.stringify(
|
||||
node
|
||||
)} ${Scrubber.stringify(prev)}`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -236,7 +239,7 @@ const applyToDraft = (editor: Editor, selection: Selection, op: Operation) => {
|
||||
if (selection == null) {
|
||||
if (!Range.isRange(newProperties)) {
|
||||
throw new Error(
|
||||
`Cannot apply an incomplete "set_selection" operation properties ${JSON.stringify(
|
||||
`Cannot apply an incomplete "set_selection" operation properties ${Scrubber.stringify(
|
||||
newProperties
|
||||
)} when there is no current selection.`
|
||||
)
|
||||
|
@@ -1,15 +1,16 @@
|
||||
import {
|
||||
Ancestor,
|
||||
Editor,
|
||||
Element,
|
||||
Location,
|
||||
Node,
|
||||
NodeEntry,
|
||||
Path,
|
||||
Point,
|
||||
Range,
|
||||
Scrubber,
|
||||
Text,
|
||||
Transforms,
|
||||
NodeEntry,
|
||||
Ancestor,
|
||||
} from '..'
|
||||
import { NodeMatch, PropsCompare, PropsMerge } from '../interfaces/editor'
|
||||
import { PointRef } from '../interfaces/point-ref'
|
||||
@@ -406,9 +407,9 @@ export const NodeTransforms: NodeTransforms = {
|
||||
properties = rest as Partial<Element>
|
||||
} else {
|
||||
throw new Error(
|
||||
`Cannot merge the node at path [${path}] with the previous sibling because it is not the same kind: ${JSON.stringify(
|
||||
`Cannot merge the node at path [${path}] with the previous sibling because it is not the same kind: ${Scrubber.stringify(
|
||||
node
|
||||
)} ${JSON.stringify(prevNode)}`
|
||||
)} ${Scrubber.stringify(prevNode)}`
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Editor, Location, Point, Range, Transforms } from '..'
|
||||
import { Editor, Location, Point, Range, Scrubber, Transforms } from '..'
|
||||
import { SelectionEdge, MoveUnit } from '../interfaces/types'
|
||||
|
||||
export interface SelectionCollapseOptions {
|
||||
@@ -132,7 +132,7 @@ export const SelectionTransforms: SelectionTransforms = {
|
||||
|
||||
if (!Range.isRange(target)) {
|
||||
throw new Error(
|
||||
`When setting the selection and the current selection is \`null\` you must provide at least an \`anchor\` and \`focus\`, but you passed: ${JSON.stringify(
|
||||
`When setting the selection and the current selection is \`null\` you must provide at least an \`anchor\` and \`focus\`, but you passed: ${Scrubber.stringify(
|
||||
target
|
||||
)}`
|
||||
)
|
||||
|
24
packages/slate/test/interfaces/Scrubber/scrubber.ts
Normal file
24
packages/slate/test/interfaces/Scrubber/scrubber.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Node, Scrubber } from 'slate'
|
||||
|
||||
export const input = {
|
||||
customField: 'some very long custom field value that will get scrubbed',
|
||||
anotherField: 'this field should not get scrambled',
|
||||
}
|
||||
|
||||
export const test = (value: Node) => {
|
||||
Scrubber.setScrubber((key, value) =>
|
||||
key === 'customField' ? '... scrubbed ...' : value
|
||||
)
|
||||
const stringified = Scrubber.stringify(value)
|
||||
Scrubber.setScrubber(undefined)
|
||||
|
||||
const unmarshaled = JSON.parse(stringified)
|
||||
return (
|
||||
// ensure that first field has been scrubbed
|
||||
unmarshaled.customField === '... scrubbed ...' &&
|
||||
// ensure that second field is unaltered
|
||||
unmarshaled.anotherField === input.anotherField
|
||||
)
|
||||
}
|
||||
|
||||
export const output = true
|
Reference in New Issue
Block a user