renusify 3.1.0 → 3.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/directive/animate/index.js +1 -1
- package/index.d.ts +73 -0
- package/package.json +38 -30
- package/plugins/auto-loader.mjs +314 -0
- package/plugins/storage/index.js +151 -22
- package/plugins/trans/DateTime.js +135 -74
- package/scripts/generate-types.mjs +83 -0
- package/tests/DateTime.test.js +596 -0
- package/plugins/crypt/crypt.js +0 -281
- package/plugins/crypt/random.js +0 -52
package/index.d.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Auto-generated Vue component type declarations
|
|
2
|
+
// Do not edit manually - run `npm run build` to update
|
|
3
|
+
declare module 'vue' {
|
|
4
|
+
export interface GlobalComponents {
|
|
5
|
+
rApp: typeof import('./components/app/index.js')['rApp']
|
|
6
|
+
rAvatar: typeof import('./components/avatar/index.js')['rAvatar']
|
|
7
|
+
rBtn: typeof import('./components/button/index.js')['rBtn']
|
|
8
|
+
rBtnConfirm: typeof import('./components/button/buttonConfirm.js')['rBtnConfirm']
|
|
9
|
+
rBtnGroup: typeof import('./components/button/buttonGroup.js')['rBtnGroup']
|
|
10
|
+
rCalendar: typeof import('./components/calendar/index.js')['rCalendar']
|
|
11
|
+
rCard: typeof import('./components/card/index.js')['rCard']
|
|
12
|
+
rCodeEditor: typeof import('./components/codeEditor/index.js')['rCodeEditor']
|
|
13
|
+
rConfirm: typeof import('./components/confirm/index.js')['rConfirm']
|
|
14
|
+
rContainer: typeof import('./components/container/index.js')['rContainer']
|
|
15
|
+
rRow: typeof import('./components/container/row.js')['rRow']
|
|
16
|
+
rCol: typeof import('./components/container/col.js')['rCol']
|
|
17
|
+
rSpacer: typeof import('./components/container/spacer.js')['rSpacer']
|
|
18
|
+
rDivider: typeof import('./components/container/divider.js')['rDivider']
|
|
19
|
+
rContent: typeof import('./components/content/index.js')['rContent']
|
|
20
|
+
rCropper: typeof import('./components/cropper/index.js')['rCropper']
|
|
21
|
+
rFloat: typeof import('./components/float/index.js')['rFloat']
|
|
22
|
+
rForm: typeof import('./components/form/index.js')['rForm']
|
|
23
|
+
rInput: typeof import('./components/form/input/index.js')['rInput']
|
|
24
|
+
rColorInput: typeof import('./components/form/colorInput/index.js')['rColorInput']
|
|
25
|
+
rDateInput: typeof import('./components/form/dateInput/index.js')['rDateInput']
|
|
26
|
+
rFileInput: typeof import('./components/form/fileInput/index.js')['rFileInput']
|
|
27
|
+
rTelInput: typeof import('./components/form/telInput/index.js')['rTelInput']
|
|
28
|
+
rJsonInput: typeof import('./components/form/jsonInput/index.js')['rJsonInput']
|
|
29
|
+
rTextEditor: typeof import('./components/form/textEditor/index.js')['rTextEditor']
|
|
30
|
+
rTextEditorPreview: typeof import('./components/form/textEditor/preview.js')['rTextEditorPreview']
|
|
31
|
+
rTimeInput: typeof import('./components/form/timeInput/index.js')['rTimeInput']
|
|
32
|
+
rTimeRangeInput: typeof import('./components/form/timeInput/range.js')['rTimeRangeInput']
|
|
33
|
+
rUniqueInput: typeof import('./components/form/uniqueInput/index.js')['rUniqueInput']
|
|
34
|
+
rAddressInput: typeof import('./components/form/addressInput/index.js')['rAddressInput']
|
|
35
|
+
rCamInput: typeof import('./components/form/camInput/index.js')['rCamInput']
|
|
36
|
+
rCheckInput: typeof import('./components/form/checkInput/index.js')['rCheckInput']
|
|
37
|
+
rCheckboxInput: typeof import('./components/form/checkboxInput/index.js')['rCheckboxInput']
|
|
38
|
+
rGroupInput: typeof import('./components/form/groupInput/index.js')['rGroupInput']
|
|
39
|
+
rMaskInput: typeof import('./components/form/maskInput/index.js')['rMaskInput']
|
|
40
|
+
rNumberInput: typeof import('./components/form/numberInput/index.js')['rNumberInput']
|
|
41
|
+
rPasswordInput: typeof import('./components/form/passwordInput/index.js')['rPasswordInput']
|
|
42
|
+
rRadioInput: typeof import('./components/form/radioInput/index.js')['rRadioInput']
|
|
43
|
+
rRangeInput: typeof import('./components/form/rangeInput/index.js')['rRangeInput']
|
|
44
|
+
rRatingInput: typeof import('./components/form/ratingInput/index.js')['rRatingInput']
|
|
45
|
+
rSelectInput: typeof import('./components/form/selectInput/index.js')['rSelectInput']
|
|
46
|
+
rSwitchInput: typeof import('./components/form/switchInput/index.js')['rSwitchInput']
|
|
47
|
+
rTextArea: typeof import('./components/form/textArea/index.js')['rTextArea']
|
|
48
|
+
rTextInput: typeof import('./components/form/textInput/index.js')['rTextInput']
|
|
49
|
+
rUnitInput: typeof import('./components/form/unitInput/index.js')['rUnitInput']
|
|
50
|
+
rFormCreator: typeof import('./components/formCreator/index.js')['rFormCreator']
|
|
51
|
+
rIcon: typeof import('./components/icon/index.js')['rIcon']
|
|
52
|
+
rImg: typeof import('./components/img/index.js')['rImg']
|
|
53
|
+
rInfinite: typeof import('./components/infinite/index.js')['rInfinite']
|
|
54
|
+
rMap: typeof import('./components/map/index.js')['rMap']
|
|
55
|
+
rMapSelect: typeof import('./components/map/select.js')['rMapSelect']
|
|
56
|
+
rMapRoute: typeof import('./components/map/route.js')['rMapRoute']
|
|
57
|
+
rMenu: typeof import('./components/menu/index.js')['rMenu']
|
|
58
|
+
rMeta: typeof import('./components/meta/index.js')['rMeta']
|
|
59
|
+
rModal: typeof import('./components/modal/index.js')['rModal']
|
|
60
|
+
rNotify: typeof import('./components/notify/index.js')['rNotify']
|
|
61
|
+
rProgressCircle: typeof import('./components/progress/circle.js')['rProgressCircle']
|
|
62
|
+
rProgressLine: typeof import('./components/progress/line.js')['rProgressLine']
|
|
63
|
+
rSearchBox: typeof import('./components/searchBox/index.js')['rSearchBox']
|
|
64
|
+
rSlider: typeof import('./components/slider/index.js')['rSlider']
|
|
65
|
+
rSwiper: typeof import('./components/swiper/index.js')['rSwiper']
|
|
66
|
+
rTable: typeof import('./components/table/index.js')['rTable']
|
|
67
|
+
rTableCrud: typeof import('./components/table/crud/index.js')['rTableCrud']
|
|
68
|
+
rTimeAgo: typeof import('./components/timeAgo/index.js')['rTimeAgo']
|
|
69
|
+
rTour: typeof import('./components/tour/index.js')['rTour']
|
|
70
|
+
rTree: typeof import('./components/tree/index.js')['rTree']
|
|
71
|
+
rHighlight: typeof import('./components/highlight/index.js')['rHighlight']
|
|
72
|
+
}
|
|
73
|
+
}
|
package/package.json
CHANGED
|
@@ -1,30 +1,38 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "renusify",
|
|
3
|
-
"version": "3.1.
|
|
4
|
-
"description": "Vue3 Framework",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"vuejs",
|
|
7
|
-
"vue framework",
|
|
8
|
-
"ui framework",
|
|
9
|
-
"component framework",
|
|
10
|
-
"ui library",
|
|
11
|
-
"component library",
|
|
12
|
-
"material components",
|
|
13
|
-
"renusify"
|
|
14
|
-
],
|
|
15
|
-
"homepage": "https://github.com/smkoBa/renusify",
|
|
16
|
-
"main": "index.js",
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "renusify",
|
|
3
|
+
"version": "3.1.4",
|
|
4
|
+
"description": "Vue3 Framework",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"vuejs",
|
|
7
|
+
"vue framework",
|
|
8
|
+
"ui framework",
|
|
9
|
+
"component framework",
|
|
10
|
+
"ui library",
|
|
11
|
+
"component library",
|
|
12
|
+
"material components",
|
|
13
|
+
"renusify"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/smkoBa/renusify",
|
|
16
|
+
"main": "index.js",
|
|
17
|
+
"types": "index.d.ts",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/smkoBa/renusify.git"
|
|
21
|
+
},
|
|
22
|
+
"author": {
|
|
23
|
+
"name": "Smko Bayazidi",
|
|
24
|
+
"email": "ba.smko@gmail.com"
|
|
25
|
+
},
|
|
26
|
+
"license": "BSD",
|
|
27
|
+
"private": false,
|
|
28
|
+
"scripts": {
|
|
29
|
+
"docs:generate": "node scripts/generate-docs.mjs",
|
|
30
|
+
"build:types": "node scripts/generate-types.mjs",
|
|
31
|
+
"build": "npm run docs:generate && npm run build:types"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"sass": "^1.69.0",
|
|
35
|
+
"vue": "^3.3.0",
|
|
36
|
+
"vue-router": "^4.5.0"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
export default function renusifyAutoPlugin(options = {}) {
|
|
5
|
+
const { prefix = 'r', debug = false } = options
|
|
6
|
+
|
|
7
|
+
const state = {
|
|
8
|
+
usedComponents: new Set(),
|
|
9
|
+
usedDirectives: new Set(),
|
|
10
|
+
componentsMap: new Map(),
|
|
11
|
+
directivesMap: new Map(),
|
|
12
|
+
rootDir: '',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
function parseExports(filePath, exclude = []) {
|
|
17
|
+
try {
|
|
18
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
19
|
+
const clean = content
|
|
20
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
21
|
+
.replace(/\/\/.*$/gm, '')
|
|
22
|
+
|
|
23
|
+
const regex = /export\s+\*\s+as\s+([a-zA-Z_]\w*)\s+from\s+['"]([^'"]+)['"]/g
|
|
24
|
+
const map = new Map()
|
|
25
|
+
let m
|
|
26
|
+
while ((m = regex.exec(clean)) !== null) {
|
|
27
|
+
const [, name, relPath] = m
|
|
28
|
+
if (exclude.includes(name)) continue
|
|
29
|
+
let p = relPath.replace(/^\.\//, '')
|
|
30
|
+
if (!p.match(/\.(js|vue|ts|mjs)$/)) p = `${p}/index.js`
|
|
31
|
+
map.set(name, p)
|
|
32
|
+
}
|
|
33
|
+
return map
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.warn(`[renusify-auto] Could not parse ${filePath}:`, err.message)
|
|
36
|
+
return new Map()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
function kebab2camel(s) {
|
|
42
|
+
return s.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase())
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
function scanFile(filePath) {
|
|
47
|
+
try {
|
|
48
|
+
const content = fs.readFileSync(filePath, 'utf-8')
|
|
49
|
+
const tmplMatch = content.match(/<template(?:\s[^>]*)?>([\s\S]*?)<\/template>/)
|
|
50
|
+
if (!tmplMatch) return
|
|
51
|
+
|
|
52
|
+
const tmpl = tmplMatch[1]
|
|
53
|
+
|
|
54
|
+
const tagRe = /<\s*\/?([a-zA-Z][a-zA-Z0-9-]*)/g
|
|
55
|
+
let m
|
|
56
|
+
while ((m = tagRe.exec(tmpl)) !== null) {
|
|
57
|
+
const tag = m[1]
|
|
58
|
+
if (!tag.startsWith(prefix)) continue
|
|
59
|
+
const camel = kebab2camel(tag)
|
|
60
|
+
if (state.componentsMap.has(camel)) state.usedComponents.add(camel)
|
|
61
|
+
else if (state.componentsMap.has(tag)) state.usedComponents.add(tag)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const dirRe = /\bv-([a-zA-Z][a-zA-Z0-9-]*)/g
|
|
65
|
+
const builtins = new Set([
|
|
66
|
+
'model', 'if', 'else', 'else-if', 'for', 'show', 'bind', 'on',
|
|
67
|
+
'slot', 'text', 'html', 'cloak', 'once', 'memo', 'pre'
|
|
68
|
+
])
|
|
69
|
+
|
|
70
|
+
while ((m = dirRe.exec(tmpl)) !== null) {
|
|
71
|
+
const d = m[1]
|
|
72
|
+
if (builtins.has(d)) continue
|
|
73
|
+
const camel = kebab2camel(d)
|
|
74
|
+
const candidates = [d, camel, `v${camel.charAt(0).toUpperCase()}${camel.slice(1)}`]
|
|
75
|
+
for (const c of candidates) {
|
|
76
|
+
if (state.directivesMap.has(c)) {
|
|
77
|
+
state.usedDirectives.add(c)
|
|
78
|
+
break
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
// ignore
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
function scanDirSync(dir) {
|
|
89
|
+
let entries
|
|
90
|
+
try {
|
|
91
|
+
entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
92
|
+
} catch {
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
for (const entry of entries) {
|
|
96
|
+
const full = path.join(dir, entry.name)
|
|
97
|
+
if (entry.isDirectory()) {
|
|
98
|
+
if (['node_modules', 'dist', '.git', '.nuxt', '.output'].includes(entry.name)) continue
|
|
99
|
+
scanDirSync(full)
|
|
100
|
+
} else if (entry.name.endsWith('.vue')) {
|
|
101
|
+
scanFile(full)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
function generateImportStatements() {
|
|
108
|
+
const comps = [...state.usedComponents].sort()
|
|
109
|
+
const dirs = [...state.usedDirectives].sort()
|
|
110
|
+
|
|
111
|
+
if (comps.length === 0 && dirs.length === 0) return ''
|
|
112
|
+
|
|
113
|
+
let code = '\n'
|
|
114
|
+
if (comps.length > 0) {
|
|
115
|
+
code += `import {\n ${comps.join(',\n ')}\n} from 'renusify/components/index.js'\n`
|
|
116
|
+
}
|
|
117
|
+
if (dirs.length > 0) {
|
|
118
|
+
const dirNames = dirs.map(name => {
|
|
119
|
+
if (name.startsWith('v') || name.startsWith('V')) {
|
|
120
|
+
return name.charAt(1).toLowerCase() + name.slice(2)
|
|
121
|
+
}
|
|
122
|
+
return name
|
|
123
|
+
})
|
|
124
|
+
code += `import { ${dirNames.join(', ')} } from 'renusify/directive/index.js'\n`
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return code
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
function generateComponentsObject() {
|
|
132
|
+
const comps = [...state.usedComponents].sort()
|
|
133
|
+
if (comps.length === 0) return null
|
|
134
|
+
return `components: {\n ${comps.join(',\n ')}\n }`
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
function generateDirectivesObject() {
|
|
139
|
+
const dirs = [...state.usedDirectives].sort()
|
|
140
|
+
if (dirs.length === 0) return null
|
|
141
|
+
const dirNames = dirs.map(name => {
|
|
142
|
+
if (name.startsWith('v') || name.startsWith('V')) {
|
|
143
|
+
return name.charAt(1).toLowerCase() + name.slice(2)
|
|
144
|
+
}
|
|
145
|
+
return name
|
|
146
|
+
})
|
|
147
|
+
return `directives: { ${dirNames.join(', ')} }`
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
name: 'vite-plugin-renusify-auto',
|
|
152
|
+
enforce: 'pre',
|
|
153
|
+
|
|
154
|
+
configResolved(config) {
|
|
155
|
+
state.rootDir = config.root
|
|
156
|
+
const renusifyPath = path.join(config.root, 'node_modules/renusify')
|
|
157
|
+
|
|
158
|
+
state.componentsMap = parseExports(
|
|
159
|
+
path.join(renusifyPath, 'components/index.js'),
|
|
160
|
+
['_register']
|
|
161
|
+
)
|
|
162
|
+
state.directivesMap = parseExports(
|
|
163
|
+
path.join(renusifyPath, 'directive/index.js'),
|
|
164
|
+
['_registers']
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
if (config.command === 'serve') {
|
|
168
|
+
state.usedComponents = new Set(state.componentsMap.keys())
|
|
169
|
+
state.usedDirectives = new Set(state.directivesMap.keys())
|
|
170
|
+
if (debug) {
|
|
171
|
+
console.log(`[renusify-auto] Dev mode: ALL ${state.componentsMap.size} components, ${state.directivesMap.size} directives`)
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
scanDirSync(path.join(config.root, 'src'))
|
|
175
|
+
if (debug) {
|
|
176
|
+
console.log(`[renusify-auto] Build mode: ${state.usedComponents.size} components, ${state.usedDirectives.size} directives`)
|
|
177
|
+
console.log(' Components:', [...state.usedComponents].join(', '))
|
|
178
|
+
console.log(' Directives:', [...state.usedDirectives].join(', '))
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
transform(code, id) {
|
|
184
|
+
if (!/main\.(js|ts)$/.test(id)) return null
|
|
185
|
+
if (id.includes('node_modules')) return null
|
|
186
|
+
|
|
187
|
+
let newCode = code
|
|
188
|
+
let modified = false
|
|
189
|
+
|
|
190
|
+
const importStmts = generateImportStatements()
|
|
191
|
+
if (importStmts) {
|
|
192
|
+
const importRegex = /^import\s+[^;]+;?\s*$/gm
|
|
193
|
+
const allImports = code.match(importRegex)
|
|
194
|
+
|
|
195
|
+
if (allImports && allImports.length > 0) {
|
|
196
|
+
const lastImport = allImports[allImports.length - 1]
|
|
197
|
+
const lastImportIndex = code.lastIndexOf(lastImport)
|
|
198
|
+
const insertPos = lastImportIndex + lastImport.length
|
|
199
|
+
newCode = newCode.slice(0, insertPos) + importStmts + newCode.slice(insertPos)
|
|
200
|
+
} else {
|
|
201
|
+
newCode = importStmts + '\n' + newCode
|
|
202
|
+
}
|
|
203
|
+
modified = true
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const renusifyRegex = /\.use\s*\(\s*renusify\s*,\s*\{/
|
|
207
|
+
const useMatch = newCode.match(renusifyRegex)
|
|
208
|
+
|
|
209
|
+
if (useMatch) {
|
|
210
|
+
const matchIndex = useMatch.index
|
|
211
|
+
const startOfOptions = matchIndex + useMatch[0].length
|
|
212
|
+
|
|
213
|
+
let braceCount = 1
|
|
214
|
+
let i = startOfOptions
|
|
215
|
+
while (i < newCode.length && braceCount > 0) {
|
|
216
|
+
if (newCode[i] === '{') braceCount++
|
|
217
|
+
else if (newCode[i] === '}') braceCount--
|
|
218
|
+
if (braceCount > 0) i++
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (braceCount === 0) {
|
|
222
|
+
const endOfOptions = i
|
|
223
|
+
const optionsContent = newCode.slice(startOfOptions, endOfOptions)
|
|
224
|
+
let cleanedOptions = optionsContent
|
|
225
|
+
|
|
226
|
+
cleanedOptions = removeProperty(cleanedOptions, 'components')
|
|
227
|
+
cleanedOptions = removeProperty(cleanedOptions, 'directives')
|
|
228
|
+
|
|
229
|
+
cleanedOptions = cleanedOptions.replace(/,(\s*)}/g, '$1}')
|
|
230
|
+
cleanedOptions = cleanedOptions.trim()
|
|
231
|
+
|
|
232
|
+
const componentsObj = generateComponentsObject()
|
|
233
|
+
const directivesObj = generateDirectivesObject()
|
|
234
|
+
|
|
235
|
+
let newOptionsContent = cleanedOptions
|
|
236
|
+
|
|
237
|
+
if (componentsObj || directivesObj) {
|
|
238
|
+
const needsComma = newOptionsContent.trim().length > 0 && !newOptionsContent.trim().endsWith(',')
|
|
239
|
+
const comma = needsComma ? ',' : ''
|
|
240
|
+
|
|
241
|
+
let additions = ''
|
|
242
|
+
if (componentsObj) additions += '\n ' + componentsObj
|
|
243
|
+
if (directivesObj) additions += (additions ? ',' : '') + '\n ' + directivesObj
|
|
244
|
+
|
|
245
|
+
if (newOptionsContent.trim().length === 0) {
|
|
246
|
+
newOptionsContent = additions + '\n '
|
|
247
|
+
} else {
|
|
248
|
+
newOptionsContent = newOptionsContent + comma + additions + '\n '
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
newCode = newCode.slice(0, startOfOptions) + newOptionsContent + newCode.slice(endOfOptions)
|
|
253
|
+
modified = true
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (modified) {
|
|
258
|
+
if (debug) {
|
|
259
|
+
console.log(`[renusify-auto] Transformed ${path.basename(id)}`)
|
|
260
|
+
}
|
|
261
|
+
return { code: newCode, map: null }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return null
|
|
265
|
+
},
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function removeProperty(optionsStr, propName) {
|
|
270
|
+
const propRegex = new RegExp(`,?\\s*${propName}\\s*:\\s*`)
|
|
271
|
+
const match = propRegex.exec(optionsStr)
|
|
272
|
+
|
|
273
|
+
if (!match) return optionsStr
|
|
274
|
+
|
|
275
|
+
const startIndex = match.index
|
|
276
|
+
const valueStart = startIndex + match[0].length
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
let endIndex = valueStart
|
|
280
|
+
const firstChar = optionsStr[valueStart]
|
|
281
|
+
|
|
282
|
+
if (firstChar === '{') {
|
|
283
|
+
|
|
284
|
+
let braceCount = 1
|
|
285
|
+
let i = valueStart + 1
|
|
286
|
+
while (i < optionsStr.length && braceCount > 0) {
|
|
287
|
+
if (optionsStr[i] === '{') braceCount++
|
|
288
|
+
else if (optionsStr[i] === '}') braceCount--
|
|
289
|
+
i++
|
|
290
|
+
}
|
|
291
|
+
endIndex = i
|
|
292
|
+
} else if (firstChar === '[') {
|
|
293
|
+
let bracketCount = 1
|
|
294
|
+
let i = valueStart + 1
|
|
295
|
+
while (i < optionsStr.length && bracketCount > 0) {
|
|
296
|
+
if (optionsStr[i] === '[') bracketCount++
|
|
297
|
+
else if (optionsStr[i] === ']') bracketCount--
|
|
298
|
+
i++
|
|
299
|
+
}
|
|
300
|
+
endIndex = i
|
|
301
|
+
} else {
|
|
302
|
+
let i = valueStart
|
|
303
|
+
while (i < optionsStr.length && optionsStr[i] !== ',' && optionsStr[i] !== '}') {
|
|
304
|
+
i++
|
|
305
|
+
}
|
|
306
|
+
endIndex = i
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (endIndex < optionsStr.length && optionsStr[endIndex] === ',') {
|
|
310
|
+
endIndex++
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return optionsStr.slice(0, startIndex) + optionsStr.slice(endIndex)
|
|
314
|
+
}
|
package/plugins/storage/index.js
CHANGED
|
@@ -1,35 +1,164 @@
|
|
|
1
1
|
class Storage {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
constructor(encryptionKey) {
|
|
3
|
+
this._key = encryptionKey || this._loadKeyFromEnv();
|
|
4
|
+
this._available = this._checkAvailability();
|
|
5
|
+
if (!this._key) {
|
|
6
|
+
console.warn('[Storage] ⚠️ No encryption key provided. Safe methods will throw.');
|
|
4
7
|
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
_loadKeyFromEnv() {
|
|
11
|
+
if (typeof import.meta !== 'undefined' && import.meta.env?.VITE_STORAGE_KEY)
|
|
12
|
+
return import.meta.env.VITE_STORAGE_KEY;
|
|
13
|
+
if (typeof process !== 'undefined' && process.env?.STORAGE_KEY)
|
|
14
|
+
return process.env.STORAGE_KEY;
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_checkAvailability() {
|
|
19
|
+
try {
|
|
20
|
+
const test = '__storage_test__';
|
|
21
|
+
localStorage.setItem(test, '1');
|
|
22
|
+
localStorage.removeItem(test);
|
|
23
|
+
return true;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
10
26
|
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
_arrayBufferToBase64(buffer) {
|
|
30
|
+
const bytes = new Uint8Array(buffer);
|
|
31
|
+
let binary = '';
|
|
32
|
+
for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
|
|
33
|
+
return btoa(binary);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_base64ToArrayBuffer(base64) {
|
|
37
|
+
const binary = atob(base64);
|
|
38
|
+
const bytes = new Uint8Array(binary.length);
|
|
39
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
40
|
+
return bytes.buffer;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async _deriveKey(password, salt) {
|
|
44
|
+
const enc = new TextEncoder();
|
|
45
|
+
const keyMaterial = await crypto.subtle.importKey(
|
|
46
|
+
'raw', enc.encode(password), 'PBKDF2', false, ['deriveKey']
|
|
47
|
+
);
|
|
48
|
+
return crypto.subtle.deriveKey(
|
|
49
|
+
{name: 'PBKDF2', salt, iterations: 100000, hash: 'SHA-256'},
|
|
50
|
+
keyMaterial,
|
|
51
|
+
{name: 'AES-GCM', length: 256},
|
|
52
|
+
false,
|
|
53
|
+
['encrypt', 'decrypt']
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async _encrypt(plaintext) {
|
|
58
|
+
const salt = crypto.getRandomValues(new Uint8Array(16));
|
|
59
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
60
|
+
const key = await this._deriveKey(this._key, salt);
|
|
61
|
+
const cipher = await crypto.subtle.encrypt(
|
|
62
|
+
{name: 'AES-GCM', iv}, key, new TextEncoder().encode(plaintext)
|
|
63
|
+
);
|
|
64
|
+
return JSON.stringify({
|
|
65
|
+
salt: this._arrayBufferToBase64(salt),
|
|
66
|
+
iv: this._arrayBufferToBase64(iv),
|
|
67
|
+
data: this._arrayBufferToBase64(cipher)
|
|
68
|
+
});
|
|
69
|
+
}
|
|
11
70
|
|
|
12
|
-
|
|
13
|
-
|
|
71
|
+
async _decrypt(payloadString) {
|
|
72
|
+
const {salt, iv, data} = JSON.parse(payloadString);
|
|
73
|
+
const key = await this._deriveKey(this._key, this._base64ToArrayBuffer(salt));
|
|
74
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
75
|
+
{name: 'AES-GCM', iv: this._base64ToArrayBuffer(iv)},
|
|
76
|
+
key,
|
|
77
|
+
this._base64ToArrayBuffer(data)
|
|
78
|
+
);
|
|
79
|
+
return new TextDecoder().decode(decrypted);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
stringify(value) {
|
|
83
|
+
return JSON.stringify(value);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
parse(value) {
|
|
87
|
+
return JSON.parse(value);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
set(key, value) {
|
|
91
|
+
if (!this._available) return false;
|
|
92
|
+
try {
|
|
93
|
+
localStorage.setItem(String(key), this.stringify(value));
|
|
94
|
+
return true;
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.error(`[Storage] set failed:`, err);
|
|
97
|
+
return false;
|
|
14
98
|
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
has(key) {
|
|
102
|
+
if (!this._available) return false;
|
|
103
|
+
return localStorage.getItem(String(key)) !== null;
|
|
104
|
+
}
|
|
15
105
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
106
|
+
get(key, def = null) {
|
|
107
|
+
if (!this._available) return def;
|
|
108
|
+
const raw = localStorage.getItem(String(key));
|
|
109
|
+
if (raw === null) return def;
|
|
110
|
+
try {
|
|
111
|
+
return this.parse(raw);
|
|
112
|
+
} catch {
|
|
113
|
+
return def;
|
|
22
114
|
}
|
|
115
|
+
}
|
|
23
116
|
|
|
24
|
-
|
|
25
|
-
|
|
117
|
+
remove(key) {
|
|
118
|
+
if (!this._available) return false;
|
|
119
|
+
localStorage.removeItem(String(key));
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
pull(key, def = null) {
|
|
124
|
+
const data = this.get(key, def);
|
|
125
|
+
this.remove(key);
|
|
126
|
+
return data;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async setSafe(key, value) {
|
|
130
|
+
if (!this._key) throw new Error('[Storage] Encryption key not configured.');
|
|
131
|
+
if (!this._available) return false;
|
|
132
|
+
try {
|
|
133
|
+
const plaintext = this.stringify(value);
|
|
134
|
+
const encrypted = await this._encrypt(plaintext);
|
|
135
|
+
localStorage.setItem(String(key), encrypted);
|
|
136
|
+
return true;
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.error(`[Storage] setSafe failed:`, err);
|
|
139
|
+
return false;
|
|
26
140
|
}
|
|
141
|
+
}
|
|
27
142
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
143
|
+
async getSafe(key, def = null) {
|
|
144
|
+
if (!this._key) throw new Error('[Storage] Encryption key not configured.');
|
|
145
|
+
if (!this._available) return def;
|
|
146
|
+
const raw = localStorage.getItem(String(key));
|
|
147
|
+
if (raw === null) return def;
|
|
148
|
+
try {
|
|
149
|
+
const decrypted = await this._decrypt(raw);
|
|
150
|
+
return this.parse(decrypted);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
console.warn(`[Storage] getSafe failed (tampered/invalid):`, err);
|
|
153
|
+
return def;
|
|
32
154
|
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async pullSafe(key, def = null) {
|
|
158
|
+
const data = await this.getSafe(key, def);
|
|
159
|
+
this.remove(key);
|
|
160
|
+
return data;
|
|
161
|
+
}
|
|
33
162
|
}
|
|
34
163
|
|
|
35
|
-
export default new Storage();
|
|
164
|
+
export default new Storage();
|