most-box 0.0.4 → 0.0.7
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/LICENSE +21 -21
- package/README.md +222 -182
- package/electron/main.js +67 -0
- package/out/404/index.html +2 -2
- package/out/404.html +2 -2
- package/out/__next.__PAGE__.txt +6 -4
- package/out/__next._full.txt +20 -17
- package/out/__next._head.txt +3 -3
- package/out/__next._index.txt +6 -5
- package/out/__next._tree.txt +4 -2
- package/out/_next/static/chunks/003jnm.v5tzw5.js +1 -0
- package/out/_next/static/chunks/00re8v.gbcywn.js +1 -0
- package/out/_next/static/chunks/00s106sbq8t9v.js +1 -0
- package/out/_next/static/chunks/012hi627qrdnn.js +1 -0
- package/out/_next/static/chunks/0174xh3wfsjm1.js +2 -0
- package/out/_next/static/chunks/01xlw8hd842-c.js +1 -0
- package/out/_next/static/chunks/02~o2nmo5pmy1.js +1 -0
- package/out/_next/static/chunks/07t.dhhokszz5.css +1 -0
- package/out/_next/static/chunks/0_wia9ofmsi1c.css +2 -0
- package/out/_next/static/chunks/0ah8fihozo2_u.js +5 -0
- package/out/_next/static/chunks/0bzupvr5gt3k9.js +31 -0
- package/out/_next/static/chunks/0d3shmwh5_nmn.js +1 -0
- package/out/_next/static/chunks/0e_h0d3ekzks8.css +1 -0
- package/out/_next/static/chunks/0gdluj423gso1.js +1 -0
- package/out/_next/static/chunks/0gmoiq06srjay.css +1 -0
- package/out/_next/static/chunks/0ho~log~~-jwp.css +1 -0
- package/out/_next/static/chunks/0imkasy7kb67u.js +1 -0
- package/out/_next/static/chunks/0jjc_b9q_ldi2.js +1 -0
- package/out/_next/static/chunks/0jl~j62iz2uvr.js +1 -0
- package/out/_next/static/chunks/0lqslm813wk_h.js +1 -0
- package/out/_next/static/chunks/{0bogtdbh.dcu1.js → 0n~dq4kpx9xxx.js} +1 -1
- package/out/_next/static/chunks/0pqt~8bl3ukh4.js +4 -0
- package/out/_next/static/chunks/0q782fxxd0lx~.js +1 -0
- package/out/_next/static/chunks/0qub_r0x_r-e9.css +1 -0
- package/out/_next/static/chunks/0slwj0c46k5cu.js +1 -0
- package/out/_next/static/chunks/0sorqk.oc6b7j.css +1 -0
- package/out/_next/static/chunks/0tapzqc6hgvx-.js +1 -0
- package/out/_next/static/chunks/0xsc7z5x8n7wg.js +1 -0
- package/out/_next/static/chunks/0zm~gys2jwl0g.js +1 -0
- package/out/_next/static/chunks/turbopack-0a_g3u0ud~jb8.js +1 -0
- package/out/_not-found/__next._full.txt +19 -15
- package/out/_not-found/__next._head.txt +3 -3
- package/out/_not-found/__next._index.txt +6 -5
- package/out/_not-found/__next._not-found.__PAGE__.txt +9 -0
- package/out/_not-found/__next._not-found.txt +3 -3
- package/out/_not-found/__next._tree.txt +2 -2
- package/out/_not-found/index.html +2 -2
- package/out/_not-found/index.txt +19 -15
- package/out/app/__next._full.txt +20 -0
- package/out/app/__next._head.txt +5 -0
- package/out/app/__next._index.txt +7 -0
- package/out/app/__next._tree.txt +2 -0
- package/out/app/__next.app.__PAGE__.txt +9 -0
- package/out/app/__next.app.txt +5 -0
- package/out/app/index.html +15 -0
- package/out/app/index.txt +20 -0
- package/out/changelog/__next._full.txt +22 -0
- package/out/changelog/__next._head.txt +5 -0
- package/out/changelog/__next._index.txt +7 -0
- package/out/changelog/__next._tree.txt +3 -0
- package/out/changelog/__next.changelog.__PAGE__.txt +10 -0
- package/out/changelog/__next.changelog.txt +5 -0
- package/out/changelog/index.html +15 -0
- package/out/changelog/index.txt +22 -0
- package/out/chat/__next._full.txt +20 -18
- package/out/chat/__next._head.txt +3 -3
- package/out/chat/__next._index.txt +6 -5
- package/out/chat/__next._tree.txt +3 -3
- package/out/chat/__next.chat.__PAGE__.txt +9 -0
- package/out/chat/__next.chat.txt +5 -4
- package/out/chat/index.html +2 -2
- package/out/chat/index.txt +20 -18
- package/out/docs/__next._full.txt +22 -0
- package/out/docs/__next._head.txt +5 -0
- package/out/docs/__next._index.txt +7 -0
- package/out/docs/__next._tree.txt +3 -0
- package/out/docs/__next.docs.__PAGE__.txt +10 -0
- package/out/docs/__next.docs.txt +5 -0
- package/out/docs/getting-started/__next._full.txt +22 -0
- package/out/docs/getting-started/__next._head.txt +5 -0
- package/out/docs/getting-started/__next._index.txt +7 -0
- package/out/docs/getting-started/__next._tree.txt +3 -0
- package/out/docs/getting-started/__next.docs.getting-started.__PAGE__.txt +10 -0
- package/out/docs/getting-started/__next.docs.getting-started.txt +5 -0
- package/out/docs/getting-started/__next.docs.txt +5 -0
- package/out/docs/getting-started/index.html +15 -0
- package/out/docs/getting-started/index.txt +22 -0
- package/out/docs/index.html +15 -0
- package/out/docs/index.txt +22 -0
- package/out/download/__next._full.txt +34 -0
- package/out/download/__next._head.txt +5 -0
- package/out/download/__next._index.txt +7 -0
- package/out/download/__next._tree.txt +4 -0
- package/out/download/__next.download.__PAGE__.txt +16 -0
- package/out/download/__next.download.txt +5 -0
- package/out/download/index.html +15 -0
- package/out/download/index.txt +34 -0
- package/out/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
- package/out/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
- package/out/fonts/jetbrains-mono-latin-600-normal.woff2 +0 -0
- package/out/fonts/jetbrains-mono-latin-700-normal.woff2 +0 -0
- package/out/index.html +2 -2
- package/out/index.txt +20 -17
- package/out/ping/__next._full.txt +21 -0
- package/out/ping/__next._head.txt +5 -0
- package/out/ping/__next._index.txt +7 -0
- package/out/ping/__next._tree.txt +4 -0
- package/out/ping/__next.ping.__PAGE__.txt +10 -0
- package/out/ping/__next.ping.txt +5 -0
- package/out/ping/index.html +15 -0
- package/out/ping/index.txt +21 -0
- package/out/pwa-512x512.png +0 -0
- package/out/web3/__next._full.txt +21 -0
- package/out/web3/__next._head.txt +5 -0
- package/out/web3/__next._index.txt +7 -0
- package/out/web3/__next._tree.txt +3 -0
- package/out/web3/__next.web3.__PAGE__.txt +9 -0
- package/out/web3/__next.web3.txt +6 -0
- package/out/web3/ed25519/__next._full.txt +20 -0
- package/out/web3/ed25519/__next._head.txt +5 -0
- package/out/web3/ed25519/__next._index.txt +7 -0
- package/out/web3/ed25519/__next._tree.txt +3 -0
- package/out/web3/ed25519/__next.web3.ed25519.__PAGE__.txt +6 -0
- package/out/web3/ed25519/__next.web3.ed25519.txt +5 -0
- package/out/web3/ed25519/__next.web3.txt +6 -0
- package/out/web3/ed25519/index.html +1 -0
- package/out/web3/ed25519/index.txt +20 -0
- package/out/web3/index.html +15 -0
- package/out/web3/index.txt +21 -0
- package/out/web3/tools/__next._full.txt +20 -0
- package/out/web3/tools/__next._head.txt +5 -0
- package/out/web3/tools/__next._index.txt +7 -0
- package/out/web3/tools/__next._tree.txt +3 -0
- package/out/web3/tools/__next.web3.tools.__PAGE__.txt +6 -0
- package/out/web3/tools/__next.web3.tools.txt +5 -0
- package/out/web3/tools/__next.web3.txt +6 -0
- package/out/web3/tools/index.html +1 -0
- package/out/web3/tools/index.txt +20 -0
- package/package.json +162 -53
- package/public/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
- package/public/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
- package/public/fonts/jetbrains-mono-latin-600-normal.woff2 +0 -0
- package/public/fonts/jetbrains-mono-latin-700-normal.woff2 +0 -0
- package/public/pwa-512x512.png +0 -0
- package/server/cli.js +3 -0
- package/server/index.js +995 -0
- package/{src → server/src}/config.js +51 -50
- package/{src → server/src}/core/cid.js +157 -150
- package/{src → server/src}/index.js +1952 -1669
- package/server/src/utils/api.js +68 -0
- package/server/src/utils/avatar.js +11 -0
- package/{src → server/src}/utils/errors.js +70 -66
- package/server/src/utils/mostWallet.js +42 -0
- package/server/src/utils/mp.js +105 -0
- package/{src → server/src}/utils/security.js +173 -169
- package/server/src/utils/userIdentity.js +93 -0
- package/cli.js +0 -2
- package/out/_next/static/chunks/00l-yd3t8dvwz.js +0 -5
- package/out/_next/static/chunks/03k8t3tgym~8~.js +0 -1
- package/out/_next/static/chunks/09vfh8lfuacc0.css +0 -1
- package/out/_next/static/chunks/0dbhjjzl8qfwv.js +0 -1
- package/out/_next/static/chunks/0f73psqhr8dre.css +0 -1
- package/out/_next/static/chunks/0fbi7z4_.4j1j.js +0 -1
- package/out/_next/static/chunks/0ht900cau6_ur.js +0 -31
- package/out/_next/static/chunks/0ohm.ia-4ec60.js +0 -1
- package/out/_next/static/chunks/0u5ydb-f0.vxl.js +0 -1
- package/out/_next/static/chunks/14t2m1on-s5v~.js +0 -1
- package/out/_next/static/chunks/turbopack-076ce9exut_h3.js +0 -1
- package/out/_not-found/__next._not-found/__PAGE__.txt +0 -5
- package/out/app.css +0 -1535
- package/out/bundle.js +0 -107
- package/out/bundle.js.map +0 -7
- package/out/chat/__next.chat/__PAGE__.txt +0 -9
- package/out/chat-page.js +0 -112
- package/out/chat.css +0 -378
- package/out/index.js +0 -148
- package/public/app.css +0 -1535
- package/public/bundle.js +0 -107
- package/public/bundle.js.map +0 -7
- package/public/chat-page.js +0 -112
- package/public/chat.css +0 -378
- package/public/index.js +0 -148
- package/server.js +0 -880
- package/src/utils/api.js +0 -6
- /package/out/_next/static/{0h4f4QFk_KC9FlSRfQACk → sV38nXrv3xVVO6wvSdFlZ}/_buildManifest.js +0 -0
- /package/out/_next/static/{0h4f4QFk_KC9FlSRfQACk → sV38nXrv3xVVO6wvSdFlZ}/_clientMiddlewareManifest.js +0 -0
- /package/out/_next/static/{0h4f4QFk_KC9FlSRfQACk → sV38nXrv3xVVO6wvSdFlZ}/_ssgManifest.js +0 -0
|
@@ -1,169 +1,173 @@
|
|
|
1
|
-
import path from 'node:path'
|
|
2
|
-
import fs from 'node:fs'
|
|
3
|
-
import crypto from 'node:crypto'
|
|
4
|
-
|
|
5
|
-
import { MAX_FILE_SIZE } from '../config.js'
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
*
|
|
13
|
-
* @
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
*
|
|
61
|
-
* @param {
|
|
62
|
-
* @param {
|
|
63
|
-
* @
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return { valid:
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import crypto from 'node:crypto'
|
|
4
|
+
|
|
5
|
+
import { MAX_FILE_SIZE } from '../config.js'
|
|
6
|
+
|
|
7
|
+
const DANGEROUS_PREFIXES = /^[\s.]+|[\s.]+$/
|
|
8
|
+
const RESERVED_NAMES = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 清理文件名以防止安全问题
|
|
12
|
+
* @param {string} filename - 原始文件名
|
|
13
|
+
* @returns {string} - 清理后的文件名
|
|
14
|
+
*/
|
|
15
|
+
export function sanitizeFilename(filename) {
|
|
16
|
+
if (typeof filename !== 'string') {
|
|
17
|
+
throw new Error('Filename must be a string')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let sanitized = filename
|
|
21
|
+
|
|
22
|
+
// 将反斜杠规范化为正斜杠(S3 风格路径)
|
|
23
|
+
sanitized = sanitized.replace(/\\/g, '/')
|
|
24
|
+
|
|
25
|
+
// 移除危险字符但保留 / 以支持文件夹路径
|
|
26
|
+
sanitized = sanitized.replace(/[<>:"|?*\x00-\x1f]/g, '_')
|
|
27
|
+
|
|
28
|
+
// 移除危险前缀/后缀
|
|
29
|
+
sanitized = sanitized.replace(DANGEROUS_PREFIXES, '')
|
|
30
|
+
|
|
31
|
+
// 防止路径遍历
|
|
32
|
+
while (sanitized.includes('..')) {
|
|
33
|
+
sanitized = sanitized.replace(/\.\./g, '_')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 规范多个连续斜杠
|
|
37
|
+
sanitized = sanitized.replace(/\/{2,}/g, '/')
|
|
38
|
+
|
|
39
|
+
// 移除首尾斜杠
|
|
40
|
+
sanitized = sanitized.replace(/^\/+|\/+$/g, '')
|
|
41
|
+
|
|
42
|
+
// 单独清理每个路径段
|
|
43
|
+
const segments = sanitized.split('/')
|
|
44
|
+
const safeSegments = segments.map(seg => {
|
|
45
|
+
let safe = seg.replace(/[<>:"|?*]/g, '_')
|
|
46
|
+
const baseName = safe.replace(/\.[^.]+$/, '')
|
|
47
|
+
if (RESERVED_NAMES.test(baseName)) {
|
|
48
|
+
safe = '_' + safe
|
|
49
|
+
}
|
|
50
|
+
return safe.substring(0, 255) || 'unnamed'
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
sanitized = safeSegments.join('/')
|
|
54
|
+
|
|
55
|
+
return sanitized || 'unnamed_file'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 验证并清理文件路径以防止路径遍历攻击
|
|
60
|
+
* @param {string} inputPath - 用户输入路径
|
|
61
|
+
* @param {object} options - 验证选项
|
|
62
|
+
* @param {string} [options.allowedBase] - 路径必须在的基础目录(可选)
|
|
63
|
+
* @returns {{ cleanPath: string, error?: string }}
|
|
64
|
+
*/
|
|
65
|
+
export function validateAndSanitizePath(inputPath, options = {}) {
|
|
66
|
+
if (typeof inputPath !== 'string') {
|
|
67
|
+
return { cleanPath: '', error: 'Path must be a string' }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let cleanPath = inputPath
|
|
71
|
+
|
|
72
|
+
cleanPath = cleanPath.replace(/[\u200B-\u200D\uFEFF\u202A-\u202E]/g, '')
|
|
73
|
+
|
|
74
|
+
cleanPath = cleanPath.replace(/"/g, '').trim()
|
|
75
|
+
|
|
76
|
+
const pathTraversalPattern = /\.\./
|
|
77
|
+
if (pathTraversalPattern.test(cleanPath)) {
|
|
78
|
+
return {
|
|
79
|
+
cleanPath: '',
|
|
80
|
+
error: 'Path traversal detected: path cannot contain ".."',
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
cleanPath = path.normalize(cleanPath)
|
|
85
|
+
|
|
86
|
+
if (options.allowedBase) {
|
|
87
|
+
const resolvedPath = path.resolve(cleanPath)
|
|
88
|
+
const allowedBase = path.resolve(options.allowedBase)
|
|
89
|
+
if (
|
|
90
|
+
resolvedPath !== allowedBase &&
|
|
91
|
+
!resolvedPath.startsWith(allowedBase + path.sep)
|
|
92
|
+
) {
|
|
93
|
+
return { cleanPath: '', error: 'Path must be within allowed directory' }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { cleanPath }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 检查文件大小是否在限制内
|
|
102
|
+
* @param {string} filePath - 文件路径
|
|
103
|
+
* @param {number} [maxSize] - 最大允许大小(字节,默认 100GB)
|
|
104
|
+
* @returns {{ valid: boolean, size?: number, error?: string }}
|
|
105
|
+
*/
|
|
106
|
+
export async function validateFileSize(filePath, maxSize = MAX_FILE_SIZE) {
|
|
107
|
+
try {
|
|
108
|
+
const stats = await fs.promises.stat(filePath)
|
|
109
|
+
const size = stats.size
|
|
110
|
+
|
|
111
|
+
if (!stats.isFile()) {
|
|
112
|
+
return { valid: false, error: 'Path is not a file' }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (size > maxSize) {
|
|
116
|
+
const maxGB = Math.round(maxSize / (1024 * 1024 * 1024))
|
|
117
|
+
return {
|
|
118
|
+
valid: false,
|
|
119
|
+
size,
|
|
120
|
+
error: `File size exceeds limit of ${maxGB} GB`,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { valid: true, size }
|
|
125
|
+
} catch (err) {
|
|
126
|
+
if (err.code === 'ENOENT') {
|
|
127
|
+
return { valid: false, error: 'File does not exist' }
|
|
128
|
+
}
|
|
129
|
+
return { valid: false, error: `Failed to check file size: ${err.message}` }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 检查目录是否可写
|
|
135
|
+
* @param {string} dirPath - 要检查的目录路径
|
|
136
|
+
* @returns {{ writable: boolean, error?: string }}
|
|
137
|
+
*/
|
|
138
|
+
export async function checkDirectoryWritable(dirPath) {
|
|
139
|
+
try {
|
|
140
|
+
if (!fs.existsSync(dirPath)) {
|
|
141
|
+
fs.mkdirSync(dirPath, { recursive: true })
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const testFile = path.join(dirPath, `.write-test-${crypto.randomUUID()}`)
|
|
145
|
+
await fs.promises.writeFile(testFile, 'test')
|
|
146
|
+
await fs.promises.unlink(testFile)
|
|
147
|
+
|
|
148
|
+
return { writable: true }
|
|
149
|
+
} catch (err) {
|
|
150
|
+
return {
|
|
151
|
+
writable: false,
|
|
152
|
+
error: `Cannot write to directory: ${err.message}`,
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 获取人类可读的文件大小字符串
|
|
159
|
+
* @param {number} bytes - 字节大小
|
|
160
|
+
* @returns {string}
|
|
161
|
+
*/
|
|
162
|
+
export function formatFileSize(bytes) {
|
|
163
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
164
|
+
let unitIndex = 0
|
|
165
|
+
let size = bytes
|
|
166
|
+
|
|
167
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
168
|
+
size /= 1024
|
|
169
|
+
unitIndex++
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return `${size.toFixed(2)} ${units[unitIndex]}`
|
|
173
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto'
|
|
2
|
+
import {
|
|
3
|
+
pbkdf2,
|
|
4
|
+
sha256,
|
|
5
|
+
getBytes,
|
|
6
|
+
Mnemonic,
|
|
7
|
+
HDNodeWallet,
|
|
8
|
+
toUtf8Bytes,
|
|
9
|
+
hexlify,
|
|
10
|
+
} from 'ethers'
|
|
11
|
+
|
|
12
|
+
const SALT_PREFIX = '/most.box/'
|
|
13
|
+
const PBKDF2_ITERATIONS = 3
|
|
14
|
+
const PBKDF2_KEY_LENGTH = 32
|
|
15
|
+
|
|
16
|
+
function generateAddressAndSeed(username, password) {
|
|
17
|
+
const salt = toUtf8Bytes(SALT_PREFIX + username)
|
|
18
|
+
const p = toUtf8Bytes(password)
|
|
19
|
+
const kdf = pbkdf2(p, salt, PBKDF2_ITERATIONS, PBKDF2_KEY_LENGTH, 'sha512')
|
|
20
|
+
const seed = getBytes(sha256(getBytes(kdf)))
|
|
21
|
+
const mnemonic = Mnemonic.entropyToPhrase(seed)
|
|
22
|
+
const account = HDNodeWallet.fromPhrase(mnemonic)
|
|
23
|
+
return {
|
|
24
|
+
address: account.address,
|
|
25
|
+
danger: hexlify(seed),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createGuestIdentity(password) {
|
|
30
|
+
const username = '匿名'
|
|
31
|
+
const { address, danger } = generateAddressAndSeed(username, password)
|
|
32
|
+
return {
|
|
33
|
+
username,
|
|
34
|
+
password,
|
|
35
|
+
address,
|
|
36
|
+
danger,
|
|
37
|
+
displayName: `匿名#${address.slice(2, 8)}`,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function createLoginIdentity(username, password) {
|
|
42
|
+
const { address, danger } = generateAddressAndSeed(username, password)
|
|
43
|
+
return {
|
|
44
|
+
username,
|
|
45
|
+
password,
|
|
46
|
+
address,
|
|
47
|
+
danger,
|
|
48
|
+
displayName: `${username}#${address.slice(-4).toUpperCase()}`,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function generateGuestPassword() {
|
|
53
|
+
return randomBytes(32).toString('hex')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function loadIdentity() {
|
|
57
|
+
if (typeof localStorage === 'undefined') return null
|
|
58
|
+
try {
|
|
59
|
+
const data = localStorage.getItem('mostbox_identity')
|
|
60
|
+
if (!data) return null
|
|
61
|
+
return JSON.parse(data)
|
|
62
|
+
} catch {
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function saveIdentity(identity) {
|
|
68
|
+
if (typeof localStorage === 'undefined') return
|
|
69
|
+
localStorage.setItem('mostbox_identity', JSON.stringify(identity))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function saveGuestIdentity(guestIdentity) {
|
|
73
|
+
if (typeof localStorage === 'undefined') return
|
|
74
|
+
localStorage.setItem('mostbox_guest_identity', JSON.stringify(guestIdentity))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function loadGuestIdentity() {
|
|
78
|
+
if (typeof localStorage === 'undefined') return null
|
|
79
|
+
try {
|
|
80
|
+
const data = localStorage.getItem('mostbox_guest_identity')
|
|
81
|
+
if (!data) return null
|
|
82
|
+
return JSON.parse(data)
|
|
83
|
+
} catch {
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function getDisplayName(address, username = '匿名') {
|
|
89
|
+
if (username === '匿名') {
|
|
90
|
+
return `匿名#${address.slice(2, 8)}`
|
|
91
|
+
}
|
|
92
|
+
return `${username}#${address.slice(-4).toUpperCase()}`
|
|
93
|
+
}
|
package/cli.js
DELETED