diff --git a/examples/sites/demos/pc/app/grid/editor/custom-edit.spec.js b/examples/sites/demos/pc/app/grid/editor/custom-edit.spec.js index 8e740e46b6..9436b7f92f 100644 --- a/examples/sites/demos/pc/app/grid/editor/custom-edit.spec.js +++ b/examples/sites/demos/pc/app/grid/editor/custom-edit.spec.js @@ -3,6 +3,5 @@ import { test, expect } from '@playwright/test' test('多行编辑', async ({ page }) => { page.on('pageerror', (exception) => expect(exception).toBeNull()) await page.goto('grid-editor#editor-custom-edit') - await expect(page.getByRole('row', { name: '1 请选择 保存 取消' }).getByRole('textbox').first()).toBeVisible() - await expect(page.getByRole('row', { name: '2 请选择 保存 取消' }).getByRole('textbox').first()).toBeVisible() + await expect(page.getByRole('textbox', { name: 'GFD 科技有限公司', exact: true })).toBeVisible() }) diff --git a/packages/mobile/components/file-upload/src/renderless/index.ts b/packages/mobile/components/file-upload/src/renderless/index.ts index dcea57ee3a..872e8bf709 100644 --- a/packages/mobile/components/file-upload/src/renderless/index.ts +++ b/packages/mobile/components/file-upload/src/renderless/index.ts @@ -30,7 +30,7 @@ import type { } from '../file-upload' import { extend } from '@mobile-root/utils/object' -import { xss, log } from '@mobile-root/utils/xss' +import { xss, log } from '@mobile-root/utils' import uploadAjax from '@mobile-root/utils/deps/upload-ajax' import { isObject } from '@mobile-root/utils/type' import { isEmptyObject } from '@mobile-root/utils/type' @@ -577,7 +577,7 @@ export const handleStart = state, vm }: Pick) => - (rawFiles: IFileUploadFile[], updateId: string, reUpload: boolean = false) => { + (rawFiles: IFileUploadFile[], updateId: string, reUpload = false) => { if (state.isHwh5) { rawFiles = handleHwh5Files(rawFiles, props.hwh5) } @@ -921,7 +921,7 @@ export const abort = export const abortDownload = ({ state }: Pick) => - (file: IFileUploadFile, batch: boolean = false) => { + (file: IFileUploadFile, batch = false) => { const cancel = (docId) => { if (!docId) return const cancels = state.downloadCancelToken[docId] @@ -2246,7 +2246,7 @@ export const getToken = export const previewFile = ({ api, props }: Pick) => - (file: IFileUploadFile, open: boolean = false) => { + (file: IFileUploadFile, open = false) => { return new Promise((resolve, reject) => { try { const tokenParams = { isOnlinePreview: true, file, type: 'preview', token: props.edm.preview.token } diff --git a/packages/renderless/src/common/index.ts b/packages/renderless/src/common/index.ts index bb00077c95..8440a3fd85 100644 --- a/packages/renderless/src/common/index.ts +++ b/packages/renderless/src/common/index.ts @@ -10,7 +10,7 @@ * */ -import { log as uLog, xss } from '@opentiny/utils' +import { xss } from '@opentiny/utils' export const KEY_CODE = { Backspace: 8, @@ -264,8 +264,4 @@ export const CASCADER = { export const version = process.env.RUNTIME_VERSION -export const log = (data, type = 'log') => { - uLog.logger[type](data) -} - export { xss } diff --git a/packages/renderless/src/tree/index.ts b/packages/renderless/src/tree/index.ts index 12c3962875..60ef3c99f2 100644 --- a/packages/renderless/src/tree/index.ts +++ b/packages/renderless/src/tree/index.ts @@ -18,7 +18,7 @@ import { on, off } from '../common/deps/dom' import { getDataset } from '../common/dataset' import { copyArray } from '../common/object' -import { log } from '../common' +import { log } from '@opentiny/utils' export const setChildren = (props) => (data) => (props.data = data) diff --git a/packages/utils/README.md b/packages/utils/README.md index 49f84f7db7..031257ec72 100644 --- a/packages/utils/README.md +++ b/packages/utils/README.md @@ -1 +1,5 @@ -# @opentiny/utils +## 安装 + +```bash +npm install --save @opentiny/utils +``` diff --git a/packages/utils/package.json b/packages/utils/package.json index ba73831fbc..aebc7e4c21 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,10 +1,10 @@ { "name": "@opentiny/utils", + "type": "module", "version": "1.0.0", "description": "nanoid console xss", "author": "", "license": "ISC", - "type": "module", "repository": { "type": "git", "url": "git@github.com:opentiny/tiny-vue.git" @@ -16,14 +16,16 @@ ], "scripts": { "build": "vite build", - "pub": "pnpm publish --no-git-checks --access=public" + "pub": "pnpm publish --no-git-checks --access=public", + "test": "vitest" }, "dependencies": { "xss": "1.0.14" }, "devDependencies": { "typescript": "catalog:", + "vite": "catalog:", "vite-plugin-dts": "~4.3.0", - "vite": "catalog:" + "vitest": "catalog:" } } diff --git a/packages/utils/src/crypt/__test__/__snapshots__/crypt.test.ts.snap b/packages/utils/src/crypt/__test__/__snapshots__/crypt.test.ts.snap new file mode 100644 index 0000000000..ea4fb6d852 --- /dev/null +++ b/packages/utils/src/crypt/__test__/__snapshots__/crypt.test.ts.snap @@ -0,0 +1,3 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`测试sha256 1`] = `"b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"`; diff --git a/packages/utils/src/crypt/__test__/crypt.test.ts b/packages/utils/src/crypt/__test__/crypt.test.ts new file mode 100644 index 0000000000..c79609ef7f --- /dev/null +++ b/packages/utils/src/crypt/__test__/crypt.test.ts @@ -0,0 +1,7 @@ +import { test, expect } from 'vitest' +import { sha256 } from '../index' + +test('测试sha256', async () => { + // 简单记录加密的结果,测试用来保证sha256算法不变化 + expect(await sha256('hello world')).toMatchSnapshot() +}) diff --git a/packages/utils/src/crypt/core.ts b/packages/utils/src/crypt/core.ts deleted file mode 100644 index 414f9edb0f..0000000000 --- a/packages/utils/src/crypt/core.ts +++ /dev/null @@ -1,341 +0,0 @@ -import { getWindow } from '../window' - -const crypto = getWindow().crypto -let randomWordsArray: any - -if (crypto) { - randomWordsArray = (mBytes: number) => { - const words = [] - - for (let i = 0; i < mBytes; i += 4) { - words.push(crypto.getRandomValues(new Uint32Array(1))[0]) - } - - return new WordArray(words, mBytes) - } -} else { - // Because there is no global crypto property in this context, cryptographically unsafe Math.random() is used. - - randomWordsArray = (mBytes: number) => { - const words = [] - - const r = (m_w: any) => { - let _m_w = m_w - let _m_z = 0x3ade68b1 - const _m_k = 0xffffffff - - return () => { - _m_z = (0x9069 * (_m_z & 0xffff) + (_m_z >> 0x10)) & _m_k - _m_w = (0x4650 * (_m_w & 0xffff) + (_m_w >> 0x10)) & _m_k - let result = ((_m_z << 0x10) + _m_w) & _m_k - result /= 0x100000000 - result += 0.5 - return result * (Math.random() > 0.5 ? 1 : -1) - } - } - - for (let i = 0, rcache; i < mBytes; i += 4) { - const _r = r((rcache || Math.random()) * 0x100000000) - - rcache = _r() * 0x3ade67b7 - words.push((_r() * 0x100000000) | 0) - } - - return new WordArray(words, mBytes) - } -} - -export class Base { - // @ts-ignore - static create(...args) { - // @ts-ignore - return new this(...args) - } - - clone() { - // @ts-ignore - const clone = new this.constructor() - Object.assign(clone, this) - return clone - } - - mixIn(properties: any) { - return Object.assign(this, properties) - } -} - -export class WordArray extends Base { - words - sigBytes - - constructor(words: Array = [], sigBytes = words.length * 4) { - super() - - let arrayTyped: any = words - // Convert buffers to uint8 - if (arrayTyped instanceof ArrayBuffer) { - arrayTyped = new Uint8Array(arrayTyped) - } - - // Convert other array views to uint8 - if ( - arrayTyped instanceof Int8Array || - arrayTyped instanceof Uint8ClampedArray || - arrayTyped instanceof Int16Array || - arrayTyped instanceof Uint16Array || - arrayTyped instanceof Int32Array || - arrayTyped instanceof Uint32Array || - arrayTyped instanceof Float32Array || - arrayTyped instanceof Float64Array - ) { - arrayTyped = new Uint8Array(arrayTyped.buffer, arrayTyped.byteOffset, arrayTyped.byteLength) - } - - // Handle Uint8Array - if (arrayTyped instanceof Uint8Array) { - // Shortcut - const typedArrayByteLength = arrayTyped.byteLength - - // Extract bytes - const _words: Array = [] - for (let i = 0; i < typedArrayByteLength; i += 1) { - _words[i >>> 2] |= arrayTyped[i] << (24 - (i % 4) * 8) - } - - this.words = _words - this.sigBytes = typedArrayByteLength - } else { - this.words = words - this.sigBytes = sigBytes - } - } - - static random = randomWordsArray - - toString(encoder = Hex) { - return encoder.stringify(this) - } - - concat(wordArray: any) { - // Shortcuts - const _words = this.words - const arrayWords = wordArray.words - const _sigBytes = this.sigBytes - const wordSigBytes = wordArray.sigBytes - - // Clamp excess bits - this.clamp() - - // Concat - if (_sigBytes % 4) { - // Copy one byte at a time - for (let i = 0; i < wordSigBytes; i += 1) { - const thatByte = (arrayWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff - _words[(_sigBytes + i) >>> 2] |= thatByte << (24 - ((_sigBytes + i) % 4) * 8) - } - } else { - // Copy one word at a time - for (let i = 0; i < wordSigBytes; i += 4) { - _words[(_sigBytes + i) >>> 2] = arrayWords[i >>> 2] - } - } - this.sigBytes += wordSigBytes - - // Chainable - return this - } - - clone() { - const clone = super.clone.call(this) - clone.words = this.words.slice(0) - - return clone - } - - clamp() { - // Shortcuts - const { words, sigBytes } = this - - words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8) - words.length = Math.ceil(sigBytes / 4) - } -} - -export const Latin1 = { - stringify(wordArray: any) { - // Shortcuts - const { words, sigBytes } = wordArray - - // Convert - const latin1Chars = [] - for (let m = 0; m < sigBytes; m += 1) { - const bite = (words[m >>> 2] >>> (24 - (m % 4) * 8)) & 0xff - latin1Chars.push(String.fromCharCode(bite)) - } - - return latin1Chars.join('') - }, - - parse(latin1Str: any) { - // Shortcut - const latin1StrLength = latin1Str.length - - // Convert - const words: Array = [] - for (let n = 0; n < latin1StrLength; n += 1) { - words[n >>> 2] |= (latin1Str.charCodeAt(n) & 0xff) << (24 - (n % 4) * 8) - } - - return new WordArray(words, latin1StrLength) - } -} - -export const Hex = { - stringify(wordArray: any) { - // Shortcuts - const { words, sigBytes } = wordArray - - // Convert - const hexChars = [] - for (let m = 0; m < sigBytes; m += 1) { - const bite = (words[m >>> 2] >>> (24 - (m % 4) * 8)) & 0xff - hexChars.push((bite >>> 4).toString(16)) - hexChars.push((bite & 0x0f).toString(16)) - } - - return hexChars.join('') - }, - - parse(hexStr: any) { - // Shortcut - const hexStrLength = hexStr.length - - // Convert - const words: Array = [] - for (let n = 0; n < hexStrLength; n += 2) { - words[n >>> 3] |= parseInt(hexStr.substr(n, 2), 16) << (24 - (n % 8) * 4) - } - - return new WordArray(words, hexStrLength / 2) - } -} - -export const UTF8 = { - stringify(wordArray: any) { - try { - return decodeURIComponent(escape(Latin1.stringify(wordArray))) - } catch (e) { - throw new Error('The UTF-8 data format is incorrect.') - } - }, - - parse(utf8Str: any) { - return Latin1.parse(unescape(encodeURIComponent(utf8Str))) - } -} - -export class BufferedBlockAlgorithm extends Base { - _minBufferSize - _nDataBytes: number = 0 - _data: any - blockSize: number = 0 - - constructor() { - super() - this._minBufferSize = 0 - } - - _append(data: any) { - let m_data = data - - if (typeof m_data === 'string') { - m_data = UTF8.parse(m_data) - } - - this._data.concat(m_data) - this._nDataBytes += m_data.sigBytes - } - - reset() { - this._data = new WordArray() - this._nDataBytes = 0 - } - - _process(doFlush: any) { - let processedWords - const { _data: data, blockSize } = this - const dataWords = data.words - const dataSigBytes = data.sigBytes - const blockSizeBytes = blockSize * 4 - - let mBlocksReady = dataSigBytes / blockSizeBytes - if (doFlush) { - mBlocksReady = Math.ceil(mBlocksReady) - } else { - mBlocksReady = Math.max((mBlocksReady | 0) - this._minBufferSize, 0) - } - - const mWordsReady = mBlocksReady * blockSize - const mBytesReady = Math.min(mWordsReady * 4, dataSigBytes) - - if (mWordsReady) { - for (let offset = 0; offset < mWordsReady; offset += blockSize) { - // @ts-ignore - this._doProcessBlock(dataWords, offset) - } - - processedWords = dataWords.splice(0, mWordsReady) - data.sigBytes -= mBytesReady - } - - return new WordArray(processedWords, mBytesReady) - } - - clone() { - const clone = super.clone.call(this) - clone._data = this._data.clone() - - return clone - } -} - -export class Hasher extends BufferedBlockAlgorithm { - cfg - _process: any - - constructor(cfg: any) { - super() - - this.cfg = Object.assign(new Base(), cfg) - this.blockSize = 512 / 32 - - this.reset() - } - - static _createHelper(SubHasher: any) { - return (message: any, cfg: any) => new SubHasher(cfg).finalize(message) - } - - update(messageUpdate: any) { - this._append(messageUpdate) - this._process() - - return this - } - - reset() { - super.reset.call(this) - // @ts-ignore - this._doReset() - } - - finalize(messageUpdate: any) { - if (messageUpdate) { - this._append(messageUpdate) - } - // @ts-ignore - const hash = this._doFinalize() - - return hash - } -} diff --git a/packages/utils/src/crypt/index.ts b/packages/utils/src/crypt/index.ts index 02954c1fa3..723537f21b 100644 --- a/packages/utils/src/crypt/index.ts +++ b/packages/utils/src/crypt/index.ts @@ -1,25 +1,14 @@ -import { WordArray } from './core' -import { SHA256 } from './sha256' +import { getWindow } from '../window' -async function digestMessage(algo: string, message: any) { +/** 生成字节流或字符串的sha256编码 */ +export async function sha256(message: ArrayBuffer | string) { const isArrayBuffer = Object.prototype.toString.call(message) === '[object ArrayBuffer]' - const msgUint8 = isArrayBuffer ? message : new TextEncoder().encode(message) // 编码为(utf-8)Uint8Array - const hashBuffer = await window.crypto.subtle.digest(algo, msgUint8) // 计算消息的哈希值 + const msgUint8 = isArrayBuffer ? message : new TextEncoder().encode(message as string) // 编码为(utf-8)Uint8Array + const hashBuffer = await getWindow().crypto.subtle.digest('SHA-256', msgUint8) // 计算消息的哈希值 const hashArray = Array.from(new Uint8Array(hashBuffer)) // 将缓冲区转换为字节数组 const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('') // 将字节数组转换为十六进制字符串 return hashHex } -export function sha256(message: any) { - if (window.crypto.subtle) { - return digestMessage('SHA-256', message) - } else { - const isArrayBuffer = Object.prototype.toString.call(message) === '[object ArrayBuffer]' - if (isArrayBuffer) { - return SHA256(new WordArray(message), '').toString() - } else { - return SHA256(message, '').toStrsha256 - } - } -} +export default { sha256 } diff --git a/packages/utils/src/crypt/sha256.ts b/packages/utils/src/crypt/sha256.ts deleted file mode 100644 index 0645ed1cd9..0000000000 --- a/packages/utils/src/crypt/sha256.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { WordArray, Hasher } from './core' - -// Initialization and round constants tables -const H: Array = [] -const K: Array = [] -const W: Array = [] - -let n = 2 -let mPrime = 0 - -const isPrime = (n: number) => { - const sqrtN = Math.sqrt(n) - for (let fact = 2; fact <= sqrtN; fact += 1) { - if (!(n % fact)) { - return false - } - } - - return true -} - -const getFractionalBits = (n: number) => ((n - (n | 0)) * 0x100000000) | 0 - -while (mPrime < 64) { - if (isPrime(n)) { - if (mPrime < 8) { - H[mPrime] = getFractionalBits(n ** (1 / 2)) - } - K[mPrime] = getFractionalBits(n ** (1 / 3)) - - mPrime += 1 - } - - n += 1 -} - -export class SHA256Algo extends Hasher { - _hash: any - declare _data: any - - _doReset() { - this._hash = new WordArray(H.slice(0)) - } - - _doProcessBlock(N: Array, offset: number) { - // Shortcut - const _W = this._hash.words - - // Working variables - let a = _W[0] - let b = _W[1] - let c = _W[2] - let d = _W[3] - let e = _W[4] - let f = _W[5] - let g = _W[6] - let h = _W[7] - - // Computation - for (let i = 0; i < 64; i += 1) { - if (i < 16) { - W[i] = N[offset + i] | 0 - } else { - const alpha0x = W[i - 15] - const alpha0 = ((alpha0x << 25) | (alpha0x >>> 7)) ^ ((alpha0x << 14) | (alpha0x >>> 18)) ^ (alpha0x >>> 3) - - const alpha1x = W[i - 2] - const alpha1 = ((alpha1x << 15) | (alpha1x >>> 17)) ^ ((alpha1x << 13) | (alpha1x >>> 19)) ^ (alpha1x >>> 10) - - W[i] = alpha0 + W[i - 7] + alpha1 + W[i - 16] - } - - const sigma0 = ((a << 30) | (a >>> 2)) ^ ((a << 19) | (a >>> 13)) ^ ((a << 10) | (a >>> 22)) - const sigma1 = ((e << 26) | (e >>> 6)) ^ ((e << 21) | (e >>> 11)) ^ ((e << 7) | (e >>> 25)) - - const ch = (e & f) ^ (~e & g) - const maj = (a & b) ^ (a & c) ^ (b & c) - - const g1 = h + sigma1 + ch + K[i] + W[i] - const g2 = sigma0 + maj - - h = g - g = f - f = e - e = (d + g1) | 0 - d = c - c = b - b = a - a = (g1 + g2) | 0 - } - - // Intermediate hash value - _W[0] = (_W[0] + a) | 0 - _W[1] = (_W[1] + b) | 0 - _W[2] = (_W[2] + c) | 0 - _W[3] = (_W[3] + d) | 0 - _W[4] = (_W[4] + e) | 0 - _W[5] = (_W[5] + f) | 0 - _W[6] = (_W[6] + g) | 0 - _W[7] = (_W[7] + h) | 0 - } - - _doFinalize() { - // Shortcuts - const data = this._data - const dataWords = data.words - - const _nBitsTotal = this._nDataBytes * 8 - const _nBitsLeft = data.sigBytes * 8 - - // Add padding - dataWords[_nBitsLeft >>> 5] |= 0x80 << (24 - (_nBitsLeft % 32)) - dataWords[(((_nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(_nBitsTotal / 0x100000000) - dataWords[(((_nBitsLeft + 64) >>> 9) << 4) + 15] = _nBitsTotal - data.sigBytes = dataWords.length * 4 - - this._process() - - return this._hash - } - - clone() { - const clone = super.clone.call(this) - clone._hash = this._hash.clone() - - return clone - } -} - -export const SHA256 = Hasher._createHelper(SHA256Algo) diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 3e8d19748d..ced6869c17 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,29 +1,11 @@ -import * as _nanoid from './nanoid/index' -import _xss from './xss/index' -import * as _logger from './logger/index' -import * as _crypt from './crypt/index' +import xss from './xss' +import log from './log' +import crypt from './crypt' -type NanoidType = typeof _nanoid -type XssType = typeof _xss -type LoggerType = typeof _logger -type CryptType = typeof _crypt -interface Default { - nanoid: NanoidType - xss: XssType - log: LoggerType - crypt: CryptType -} +export { xss, log, crypt } -const def: Default = { - nanoid: _nanoid, - xss: _xss, - log: _logger, - crypt: _crypt +export default { + xss, + log, + crypt } - -export const nanoid: NanoidType = _nanoid -export const xss: XssType = _xss -export const log: LoggerType = _logger -export const crypt: CryptType = _crypt - -export default def diff --git a/packages/utils/src/log/index.ts b/packages/utils/src/log/index.ts new file mode 100644 index 0000000000..65acdc66d7 --- /dev/null +++ b/packages/utils/src/log/index.ts @@ -0,0 +1,7 @@ +import { getWindow } from '../window' + +const _win: any = getWindow() +/** 使用 log.logger.xxx 代替 window.console.xxx, 避免语法警告 */ +export const log = { logger: _win.console as Console } + +export default log diff --git a/packages/utils/src/logger/README.md b/packages/utils/src/logger/README.md deleted file mode 100644 index fa2d19b67b..0000000000 --- a/packages/utils/src/logger/README.md +++ /dev/null @@ -1,27 +0,0 @@ -## 安装 - -```bash -npm install --save @opentiny/utils -``` - -## 使用 - -logger:打印日志 - -switchLogger:控制打印开关 - -setLogger:自定义打印函数 - -```js -import { log } from '@opentiny/utils' - -const { logger, switchLogger, setLogger } = log - -logger.log(123) // => log "123" - -logger.warn(123) // => error "123" - -switchLogger(true) // 开启打印开关 - -setLogger('log', () => {}) // 自定义log方法 -``` diff --git a/packages/utils/src/logger/index.ts b/packages/utils/src/logger/index.ts deleted file mode 100644 index 381f1c1d93..0000000000 --- a/packages/utils/src/logger/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { getWindow } from '../window' - -type _initPrint = (cls: any) => any -type _log = (cls: any, key: string) => () => void -type _switchLogger = (flag: boolean) => void -type _setLogger = (type: string, fn: any) => void - -const _win: any = getWindow() -const _cnsl = 'console' -const _console = _win[_cnsl] || {} -let isOpen = true -const _print: any = {} - -const log: _log = (csl, type) => { - return function (...args) { - if (!isOpen) return - if (csl[type] && typeof csl[type] === 'function') { - csl[type](...args) - } - } -} - -const initPrint: _initPrint = (csl) => { - Object.keys(csl).forEach((type) => { - _print[type] = log(csl, type) - }) - return _print -} - -export const switchLogger: _switchLogger = (flag) => { - isOpen = !!flag -} - -export const setLogger: _setLogger = (type, fn) => { - if (_print && Object.hasOwnProperty.call(_print, type)) { - _print[type] = fn - } -} - -export const logger = initPrint(_console) - -export default logger diff --git a/packages/utils/src/nanoid/README.md b/packages/utils/src/nanoid/README.md deleted file mode 100644 index b60d2b20bc..0000000000 --- a/packages/utils/src/nanoid/README.md +++ /dev/null @@ -1,52 +0,0 @@ -## 安装 - -```bash -npm install --save @opentiny/utils -``` - -## 阻塞 - -```js -import { nanoid } from '@opentiny/utils' - -const { api } = nanoid - -model.id = api.nanoid() // => "V1StGXR8_Z5jdHi6B-myT" -``` - -```js -api.nanoid(10) // => "IRFa-VaY2b" -``` - -## 定制字母表和长度 - -```js -import { nanoid } from '@opentiny/utils' - -const { api } = nanoid -// eslint-disable-next-line no-import-assign -const nanoid = api.customAlphabet('1234567890abcdef', 10) - -model.id = nanoid() // => "4f90d13a42" -``` - -```js -import { nanoid } from '@opentiny/utils' - -const { api } = nanoid -// eslint-disable-next-line no-import-assign -const nanoid = api.customAlphabet('1234567890abcdef', 10) - -model.id = nanoid(5) // => "f01a2" -``` - -## 模拟 Math.random - -Nano ID 可以生成随机字符串,但在某些场景下仍然需要随机数。基于 window.crypto.getRandomValues 提供函数生成随机数。 - -```js -import { nanoid } from '@opentiny/utils' - -const { random } = nanoid -random() // => 0.3743718267358774 -``` diff --git a/packages/utils/src/nanoid/index.ts b/packages/utils/src/nanoid/index.ts deleted file mode 100644 index 11c419c19e..0000000000 --- a/packages/utils/src/nanoid/index.ts +++ /dev/null @@ -1,63 +0,0 @@ -import * as _nanoid from './nanoid' -import { isWeb, getWindow } from '../window' - -type TypedArray = Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array -type GetRandomValues = (array: TypedArray) => TypedArray -type Nanoid = (size?: number) => string -type CustomAlphabet = (alphabet: string, defaultSize?: number) => Nanoid -type Random = (p?: void) => number - -interface API { - urlAlphabet: string - nanoid: Nanoid - customAlphabet: CustomAlphabet -} - -function isIE(window: any): boolean { - return isWeb() && (window.document.all || window.document.documentMode) && !window.crypto && window.msCrypto -} - -function initForIE(window: any): void { - if (isIE(window)) { - window.crypto = window.msCrypto - - const getRandomValuesDef: GetRandomValues = window.crypto.getRandomValues - - window.crypto.getRandomValues = function (array: TypedArray): Array { - const values: TypedArray = getRandomValuesDef.call(window.crypto, array) - const result: Array = [] - - for (let i = 0; i < array.length; i++) { - result[i] = values[i] - } - - return result - } - } -} - -const _win: any = getWindow() - -initForIE(_win) - -const MAX_UINT32_PLUS_ONE = 4294967296 - -const urlAlphabet: string = _nanoid.urlAlphabet -const nanoid: Nanoid = _nanoid.nanoid -const customAlphabet: CustomAlphabet = _nanoid.customAlphabet - -export const random: Random = () => { - if (!isWeb()) { - return 0 - } - - return _win.crypto.getRandomValues(new _win.Uint32Array(1))[0] / MAX_UINT32_PLUS_ONE -} - -export const api: API = { - urlAlphabet, - nanoid, - customAlphabet -} - -export default api diff --git a/packages/utils/src/nanoid/nanoid.ts b/packages/utils/src/nanoid/nanoid.ts deleted file mode 100644 index 0f64910bde..0000000000 --- a/packages/utils/src/nanoid/nanoid.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { getWindow } from '../window' - -const _win: any = getWindow() -const reverseUrlAlphabet = 'tcirzywvqlkjhgfbZQG_FLOWHSUBDNIMYREVKCAJxp57XP043891T62-modnaesu' -const urlAlphabet: string = reverseUrlAlphabet.split('').reverse().join('') - -let buffer: Uint8Array -let bufferOffset: number - -const allocBuffer = (bytes: number): Uint8Array => new Uint8Array(new ArrayBuffer(bytes)) - -const randomFill = (buffer: Uint8Array): Uint8Array => _win.crypto.getRandomValues(buffer) - -const defFillPool = (bytes: number) => { - if (!buffer || buffer.length < bytes) { - buffer = allocBuffer(bytes * 128) - - randomFill(buffer) - - bufferOffset = 0 - } else if (bufferOffset + bytes > buffer.length) { - randomFill(buffer) - - bufferOffset = 0 - } - - bufferOffset += bytes -} - -const nanoid = (size = 21) => { - defFillPool((size -= 0)) - - let uniq = '' - - for (let i: number = bufferOffset - size; i < bufferOffset; i++) { - uniq += urlAlphabet[buffer[i] & 63] - } - - return uniq -} - -const defRandomFunc = (bytes: number) => { - defFillPool((bytes -= 0)) - - return buffer.subarray(bufferOffset - bytes, bufferOffset) -} - -const defCustomRandom = (alphabet: string, defaultSize: number, randomFunc: (bytes: number) => Uint8Array) => { - const mask: number = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1 - const step: number = Math.ceil((1.6 * mask * defaultSize) / alphabet.length) - - return (size: number = defaultSize) => { - let uniq = '' - - while (true) { - const bytes: Uint8Array = randomFunc(step) - let i: number = step - - while (i--) { - uniq += alphabet[bytes[i] & mask] || '' - - if (uniq.length === size) return uniq - } - } - } -} - -const customAlphabet = (alphabet: string, defaultSize = 21) => defCustomRandom(alphabet, defaultSize, defRandomFunc) - -export { urlAlphabet, nanoid, customAlphabet } diff --git a/packages/utils/src/xss/__test__/xss.test.ts b/packages/utils/src/xss/__test__/xss.test.ts new file mode 100644 index 0000000000..1994cc7271 --- /dev/null +++ b/packages/utils/src/xss/__test__/xss.test.ts @@ -0,0 +1,17 @@ +import { expect, test } from 'vitest' +import * as xss from '../index' + +test('测试 filterUrl,filterHtml, 整个组件库只用到这2个函数', async () => { + const { filterHtml, filterUrl } = xss.default + + // 过滤DOM中的危险语句 + expect(filterHtml(`Click Me`)).toMatchInlineSnapshot(`"Click Me"`) + + // 过滤控制字符 + expect(filterUrl(`hello\uFEFFworld`)).toMatchInlineSnapshot(`"helloworld"`) + // 过滤可执行代码 + expect(filterUrl(`javascript:alert('XSS')`)).toMatchInlineSnapshot(`""`) + expect(filterUrl(`data:text/html,

xss

`)).toMatchInlineSnapshot(`""`) + // 正常字符 + expect(filterUrl(`https://s.com/user`)).toMatchInlineSnapshot(`"https://s.com/user"`) +}) diff --git a/packages/vue/src/grid/package.json b/packages/vue/src/grid/package.json index 3fbabf144d..fa9940491a 100644 --- a/packages/vue/src/grid/package.json +++ b/packages/vue/src/grid/package.json @@ -26,10 +26,11 @@ "@opentiny/vue-renderless": "workspace:~", "@opentiny/vue-tag": "workspace:~", "@opentiny/vue-theme": "workspace:~", - "@opentiny/vue-tooltip": "workspace:~" + "@opentiny/vue-tooltip": "workspace:~", + "@opentiny/utils": "workspace:~" }, "devDependencies": { "@opentiny-internal/vue-test-utils": "workspace:*", "vitest": "catalog:" } -} +} \ No newline at end of file diff --git a/packages/vue/src/grid/src/tools/logger.ts b/packages/vue/src/grid/src/tools/logger.ts index 58a7478435..7d035c5dd1 100644 --- a/packages/vue/src/grid/src/tools/logger.ts +++ b/packages/vue/src/grid/src/tools/logger.ts @@ -1,4 +1,4 @@ -import { log } from '@opentiny/vue-renderless/common' +import { log } from '@opentiny/utils' import GlobalConfig from '../config' const outLog = (type) => (message, detail) => { @@ -8,7 +8,7 @@ const outLog = (type) => (message, detail) => { msg += `: ${detail}` } - log(msg, type) + log.logger.log(msg, type) return msg }