diff --git a/packages/mobile/components/file-upload/src/renderless/index.ts b/packages/mobile/components/file-upload/src/renderless/index.ts index 872e8bf709..dcea57ee3a 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' +import { xss, log } from '@mobile-root/utils/xss' 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 = false) => { + (rawFiles: IFileUploadFile[], updateId: string, reUpload: boolean = false) => { if (state.isHwh5) { rawFiles = handleHwh5Files(rawFiles, props.hwh5) } @@ -921,7 +921,7 @@ export const abort = export const abortDownload = ({ state }: Pick) => - (file: IFileUploadFile, batch = false) => { + (file: IFileUploadFile, batch: boolean = 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 = false) => { + (file: IFileUploadFile, open: boolean = 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 8440a3fd85..bb00077c95 100644 --- a/packages/renderless/src/common/index.ts +++ b/packages/renderless/src/common/index.ts @@ -10,7 +10,7 @@ * */ -import { xss } from '@opentiny/utils' +import { log as uLog, xss } from '@opentiny/utils' export const KEY_CODE = { Backspace: 8, @@ -264,4 +264,8 @@ 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 60ef3c99f2..12c3962875 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 '@opentiny/utils' +import { log } from '../common' export const setChildren = (props) => (data) => (props.data = data) diff --git a/packages/utils/README.md b/packages/utils/README.md index 031257ec72..49f84f7db7 100644 --- a/packages/utils/README.md +++ b/packages/utils/README.md @@ -1,5 +1 @@ -## 安装 - -```bash -npm install --save @opentiny/utils -``` +# @opentiny/utils diff --git a/packages/utils/package.json b/packages/utils/package.json index aebc7e4c21..ba73831fbc 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,16 +16,14 @@ ], "scripts": { "build": "vite build", - "pub": "pnpm publish --no-git-checks --access=public", - "test": "vitest" + "pub": "pnpm publish --no-git-checks --access=public" }, "dependencies": { "xss": "1.0.14" }, "devDependencies": { "typescript": "catalog:", - "vite": "catalog:", "vite-plugin-dts": "~4.3.0", - "vitest": "catalog:" + "vite": "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 deleted file mode 100644 index ea4fb6d852..0000000000 --- a/packages/utils/src/crypt/__test__/__snapshots__/crypt.test.ts.snap +++ /dev/null @@ -1,3 +0,0 @@ -// 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 deleted file mode 100644 index c79609ef7f..0000000000 --- a/packages/utils/src/crypt/__test__/crypt.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -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 new file mode 100644 index 0000000000..414f9edb0f --- /dev/null +++ b/packages/utils/src/crypt/core.ts @@ -0,0 +1,341 @@ +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 723537f21b..02954c1fa3 100644 --- a/packages/utils/src/crypt/index.ts +++ b/packages/utils/src/crypt/index.ts @@ -1,14 +1,25 @@ -import { getWindow } from '../window' +import { WordArray } from './core' +import { SHA256 } from './sha256' -/** 生成字节流或字符串的sha256编码 */ -export async function sha256(message: ArrayBuffer | string) { +async function digestMessage(algo: string, message: any) { const isArrayBuffer = Object.prototype.toString.call(message) === '[object ArrayBuffer]' - const msgUint8 = isArrayBuffer ? message : new TextEncoder().encode(message as string) // 编码为(utf-8)Uint8Array - const hashBuffer = await getWindow().crypto.subtle.digest('SHA-256', msgUint8) // 计算消息的哈希值 + const msgUint8 = isArrayBuffer ? message : new TextEncoder().encode(message) // 编码为(utf-8)Uint8Array + const hashBuffer = await window.crypto.subtle.digest(algo, msgUint8) // 计算消息的哈希值 const hashArray = Array.from(new Uint8Array(hashBuffer)) // 将缓冲区转换为字节数组 const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('') // 将字节数组转换为十六进制字符串 return hashHex } -export default { sha256 } +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 + } + } +} diff --git a/packages/utils/src/crypt/sha256.ts b/packages/utils/src/crypt/sha256.ts new file mode 100644 index 0000000000..0645ed1cd9 --- /dev/null +++ b/packages/utils/src/crypt/sha256.ts @@ -0,0 +1,130 @@ +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 ced6869c17..3e8d19748d 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,11 +1,29 @@ -import xss from './xss' -import log from './log' -import crypt from './crypt' +import * as _nanoid from './nanoid/index' +import _xss from './xss/index' +import * as _logger from './logger/index' +import * as _crypt from './crypt/index' -export { xss, log, 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 default { - xss, - log, - crypt +const def: Default = { + nanoid: _nanoid, + xss: _xss, + log: _logger, + crypt: _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 deleted file mode 100644 index 65acdc66d7..0000000000 --- a/packages/utils/src/log/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -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 new file mode 100644 index 0000000000..fa2d19b67b --- /dev/null +++ b/packages/utils/src/logger/README.md @@ -0,0 +1,27 @@ +## 安装 + +```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 new file mode 100644 index 0000000000..381f1c1d93 --- /dev/null +++ b/packages/utils/src/logger/index.ts @@ -0,0 +1,42 @@ +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 new file mode 100644 index 0000000000..b60d2b20bc --- /dev/null +++ b/packages/utils/src/nanoid/README.md @@ -0,0 +1,52 @@ +## 安装 + +```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 new file mode 100644 index 0000000000..11c419c19e --- /dev/null +++ b/packages/utils/src/nanoid/index.ts @@ -0,0 +1,63 @@ +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 new file mode 100644 index 0000000000..0f64910bde --- /dev/null +++ b/packages/utils/src/nanoid/nanoid.ts @@ -0,0 +1,70 @@ +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 deleted file mode 100644 index 1994cc7271..0000000000 --- a/packages/utils/src/xss/__test__/xss.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -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/src/tools/logger.ts b/packages/vue/src/grid/src/tools/logger.ts index 7d035c5dd1..58a7478435 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/utils' +import { log } from '@opentiny/vue-renderless/common' import GlobalConfig from '../config' const outLog = (type) => (message, detail) => { @@ -8,7 +8,7 @@ const outLog = (type) => (message, detail) => { msg += `: ${detail}` } - log.logger.log(msg, type) + log(msg, type) return msg }