most-box 0.0.2 → 0.0.6
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 -156
- package/electron/main.js +67 -0
- package/out/404/index.html +15 -0
- package/out/404.html +15 -0
- package/out/__next.__PAGE__.txt +11 -0
- package/out/__next._full.txt +21 -0
- package/out/__next._head.txt +5 -0
- package/out/__next._index.txt +7 -0
- package/out/__next._tree.txt +4 -0
- package/out/_next/static/alUUgRz4oMlw4EtULOYfV/_buildManifest.js +11 -0
- package/out/_next/static/alUUgRz4oMlw4EtULOYfV/_clientMiddlewareManifest.js +1 -0
- package/out/_next/static/alUUgRz4oMlw4EtULOYfV/_ssgManifest.js +1 -0
- package/out/_next/static/chunks/00s106sbq8t9v.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/02ou_44kkb5dz.js +1 -0
- package/out/_next/static/chunks/02pr2b_eos3~h.js +1 -0
- package/out/_next/static/chunks/03~yq9q893hmn.js +1 -0
- package/out/_next/static/chunks/07lsjkarm1p9f.css +1 -0
- package/out/_next/static/chunks/0_-ccbcyh_o30.css +1 -0
- package/out/_next/static/chunks/0_b839~4.q324.js +1 -0
- package/out/_next/static/chunks/0_sna3wdypbzr.js +1 -0
- package/out/_next/static/chunks/0_wia9ofmsi1c.css +2 -0
- package/out/_next/static/chunks/0byj66sc-9o0g.js +1 -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/0du450zbk4kq_.js +1 -0
- package/out/_next/static/chunks/0e_h0d3ekzks8.css +1 -0
- package/out/_next/static/chunks/0ho~log~~-jwp.css +1 -0
- package/out/_next/static/chunks/0ibjp~7qzxfjv.js +5 -0
- package/out/_next/static/chunks/0imvn_arv36xt.css +1 -0
- package/out/_next/static/chunks/0j9~17180dl8j.js +1 -0
- package/out/_next/static/chunks/0ji.28mehrvdp.js +1 -0
- package/out/_next/static/chunks/0jl~j62iz2uvr.js +1 -0
- package/out/_next/static/chunks/0nct0fubs64d-.js +1 -0
- package/out/_next/static/chunks/0n~dq4kpx9xxx.js +1 -0
- package/out/_next/static/chunks/0pqt~8bl3ukh4.js +4 -0
- package/out/_next/static/chunks/0q7ck9f.90_i9.js +1 -0
- package/out/_next/static/chunks/0qub_r0x_r-e9.css +1 -0
- package/out/_next/static/chunks/0rr4gwjp9z~9a.js +1 -0
- package/out/_next/static/chunks/0ry.po.a~iu4p.js +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/11dalasm30arx.js +1 -0
- package/out/_next/static/chunks/turbopack-0a_g3u0ud~jb8.js +1 -0
- package/out/_not-found/__next._full.txt +20 -0
- package/out/_not-found/__next._head.txt +5 -0
- package/out/_not-found/__next._index.txt +7 -0
- package/out/_not-found/__next._not-found.__PAGE__.txt +9 -0
- package/out/_not-found/__next._not-found.txt +5 -0
- package/out/_not-found/__next._tree.txt +2 -0
- package/out/_not-found/index.html +15 -0
- package/out/_not-found/index.txt +20 -0
- 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 +21 -0
- package/out/chat/__next._head.txt +5 -0
- package/out/chat/__next._index.txt +7 -0
- package/out/chat/__next._tree.txt +3 -0
- package/out/chat/__next.chat.__PAGE__.txt +9 -0
- package/out/chat/__next.chat.txt +6 -0
- package/out/chat/index.html +15 -0
- package/out/chat/index.txt +21 -0
- 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/favicon.ico +0 -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 +15 -0
- package/out/index.txt +21 -0
- package/out/lottery/__next._full.txt +21 -0
- package/out/lottery/__next._head.txt +5 -0
- package/out/lottery/__next._index.txt +7 -0
- package/out/lottery/__next._tree.txt +3 -0
- package/out/lottery/__next.lottery.__PAGE__.txt +9 -0
- package/out/lottery/__next.lottery.txt +6 -0
- package/out/lottery/index.html +15 -0
- package/out/lottery/index.txt +21 -0
- 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 -48
- 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 +963 -0
- package/{src → server/src}/config.js +51 -39
- package/{src → server/src}/core/cid.js +157 -146
- package/{src → server/src}/index.js +1950 -1201
- 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 +117 -0
- package/{src → server/src}/utils/security.js +173 -169
- package/server/src/utils/userIdentity.js +93 -0
- package/build.mjs +0 -40
- package/cli.js +0 -2
- package/public/app.css +0 -1519
- package/public/app.jsx +0 -1543
- package/public/bundle.css +0 -1
- package/public/bundle.js +0 -107
- package/public/error-boundary.jsx +0 -50
- package/public/index.html +0 -16
- package/public/index.jsx +0 -20
- package/server.js +0 -698
|
@@ -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/build.mjs
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import * as esbuild from 'esbuild'
|
|
2
|
-
import fs from 'fs'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import { fileURLToPath } from 'url'
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
7
|
-
const publicDir = path.join(__dirname, 'public')
|
|
8
|
-
const outFile = path.join(publicDir, 'bundle.js')
|
|
9
|
-
|
|
10
|
-
const isWatch = process.argv.includes('--watch')
|
|
11
|
-
const isDev = process.argv.includes('--dev')
|
|
12
|
-
|
|
13
|
-
const buildOptions = {
|
|
14
|
-
entryPoints: [path.join(publicDir, 'index.jsx')],
|
|
15
|
-
bundle: true,
|
|
16
|
-
outfile: outFile,
|
|
17
|
-
format: 'esm',
|
|
18
|
-
jsx: 'automatic',
|
|
19
|
-
jsxImportSource: 'react',
|
|
20
|
-
loader: {
|
|
21
|
-
'.js': 'jsx',
|
|
22
|
-
'.jsx': 'jsx',
|
|
23
|
-
},
|
|
24
|
-
define: {
|
|
25
|
-
'process.env.NODE_ENV': isDev ? '"development"' : '"production"'
|
|
26
|
-
},
|
|
27
|
-
minify: !isDev,
|
|
28
|
-
sourcemap: isDev,
|
|
29
|
-
target: ['es2020'],
|
|
30
|
-
logLevel: 'info',
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (isWatch) {
|
|
34
|
-
const ctx = await esbuild.context(buildOptions)
|
|
35
|
-
await ctx.watch()
|
|
36
|
-
console.log('[Build] Watching for changes...')
|
|
37
|
-
} else {
|
|
38
|
-
await esbuild.build(buildOptions)
|
|
39
|
-
console.log('[Build] Done.')
|
|
40
|
-
}
|
package/cli.js
DELETED