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