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.
Files changed (181) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +222 -156
  3. package/electron/main.js +67 -0
  4. package/out/404/index.html +15 -0
  5. package/out/404.html +15 -0
  6. package/out/__next.__PAGE__.txt +11 -0
  7. package/out/__next._full.txt +21 -0
  8. package/out/__next._head.txt +5 -0
  9. package/out/__next._index.txt +7 -0
  10. package/out/__next._tree.txt +4 -0
  11. package/out/_next/static/alUUgRz4oMlw4EtULOYfV/_buildManifest.js +11 -0
  12. package/out/_next/static/alUUgRz4oMlw4EtULOYfV/_clientMiddlewareManifest.js +1 -0
  13. package/out/_next/static/alUUgRz4oMlw4EtULOYfV/_ssgManifest.js +1 -0
  14. package/out/_next/static/chunks/00s106sbq8t9v.js +1 -0
  15. package/out/_next/static/chunks/0174xh3wfsjm1.js +2 -0
  16. package/out/_next/static/chunks/01xlw8hd842-c.js +1 -0
  17. package/out/_next/static/chunks/02ou_44kkb5dz.js +1 -0
  18. package/out/_next/static/chunks/02pr2b_eos3~h.js +1 -0
  19. package/out/_next/static/chunks/03~yq9q893hmn.js +1 -0
  20. package/out/_next/static/chunks/07lsjkarm1p9f.css +1 -0
  21. package/out/_next/static/chunks/0_-ccbcyh_o30.css +1 -0
  22. package/out/_next/static/chunks/0_b839~4.q324.js +1 -0
  23. package/out/_next/static/chunks/0_sna3wdypbzr.js +1 -0
  24. package/out/_next/static/chunks/0_wia9ofmsi1c.css +2 -0
  25. package/out/_next/static/chunks/0byj66sc-9o0g.js +1 -0
  26. package/out/_next/static/chunks/0bzupvr5gt3k9.js +31 -0
  27. package/out/_next/static/chunks/0d3shmwh5_nmn.js +1 -0
  28. package/out/_next/static/chunks/0du450zbk4kq_.js +1 -0
  29. package/out/_next/static/chunks/0e_h0d3ekzks8.css +1 -0
  30. package/out/_next/static/chunks/0ho~log~~-jwp.css +1 -0
  31. package/out/_next/static/chunks/0ibjp~7qzxfjv.js +5 -0
  32. package/out/_next/static/chunks/0imvn_arv36xt.css +1 -0
  33. package/out/_next/static/chunks/0j9~17180dl8j.js +1 -0
  34. package/out/_next/static/chunks/0ji.28mehrvdp.js +1 -0
  35. package/out/_next/static/chunks/0jl~j62iz2uvr.js +1 -0
  36. package/out/_next/static/chunks/0nct0fubs64d-.js +1 -0
  37. package/out/_next/static/chunks/0n~dq4kpx9xxx.js +1 -0
  38. package/out/_next/static/chunks/0pqt~8bl3ukh4.js +4 -0
  39. package/out/_next/static/chunks/0q7ck9f.90_i9.js +1 -0
  40. package/out/_next/static/chunks/0qub_r0x_r-e9.css +1 -0
  41. package/out/_next/static/chunks/0rr4gwjp9z~9a.js +1 -0
  42. package/out/_next/static/chunks/0ry.po.a~iu4p.js +1 -0
  43. package/out/_next/static/chunks/0slwj0c46k5cu.js +1 -0
  44. package/out/_next/static/chunks/0sorqk.oc6b7j.css +1 -0
  45. package/out/_next/static/chunks/11dalasm30arx.js +1 -0
  46. package/out/_next/static/chunks/turbopack-0a_g3u0ud~jb8.js +1 -0
  47. package/out/_not-found/__next._full.txt +20 -0
  48. package/out/_not-found/__next._head.txt +5 -0
  49. package/out/_not-found/__next._index.txt +7 -0
  50. package/out/_not-found/__next._not-found.__PAGE__.txt +9 -0
  51. package/out/_not-found/__next._not-found.txt +5 -0
  52. package/out/_not-found/__next._tree.txt +2 -0
  53. package/out/_not-found/index.html +15 -0
  54. package/out/_not-found/index.txt +20 -0
  55. package/out/app/__next._full.txt +20 -0
  56. package/out/app/__next._head.txt +5 -0
  57. package/out/app/__next._index.txt +7 -0
  58. package/out/app/__next._tree.txt +2 -0
  59. package/out/app/__next.app.__PAGE__.txt +9 -0
  60. package/out/app/__next.app.txt +5 -0
  61. package/out/app/index.html +15 -0
  62. package/out/app/index.txt +20 -0
  63. package/out/changelog/__next._full.txt +22 -0
  64. package/out/changelog/__next._head.txt +5 -0
  65. package/out/changelog/__next._index.txt +7 -0
  66. package/out/changelog/__next._tree.txt +3 -0
  67. package/out/changelog/__next.changelog.__PAGE__.txt +10 -0
  68. package/out/changelog/__next.changelog.txt +5 -0
  69. package/out/changelog/index.html +15 -0
  70. package/out/changelog/index.txt +22 -0
  71. package/out/chat/__next._full.txt +21 -0
  72. package/out/chat/__next._head.txt +5 -0
  73. package/out/chat/__next._index.txt +7 -0
  74. package/out/chat/__next._tree.txt +3 -0
  75. package/out/chat/__next.chat.__PAGE__.txt +9 -0
  76. package/out/chat/__next.chat.txt +6 -0
  77. package/out/chat/index.html +15 -0
  78. package/out/chat/index.txt +21 -0
  79. package/out/docs/__next._full.txt +22 -0
  80. package/out/docs/__next._head.txt +5 -0
  81. package/out/docs/__next._index.txt +7 -0
  82. package/out/docs/__next._tree.txt +3 -0
  83. package/out/docs/__next.docs.__PAGE__.txt +10 -0
  84. package/out/docs/__next.docs.txt +5 -0
  85. package/out/docs/getting-started/__next._full.txt +22 -0
  86. package/out/docs/getting-started/__next._head.txt +5 -0
  87. package/out/docs/getting-started/__next._index.txt +7 -0
  88. package/out/docs/getting-started/__next._tree.txt +3 -0
  89. package/out/docs/getting-started/__next.docs.getting-started.__PAGE__.txt +10 -0
  90. package/out/docs/getting-started/__next.docs.getting-started.txt +5 -0
  91. package/out/docs/getting-started/__next.docs.txt +5 -0
  92. package/out/docs/getting-started/index.html +15 -0
  93. package/out/docs/getting-started/index.txt +22 -0
  94. package/out/docs/index.html +15 -0
  95. package/out/docs/index.txt +22 -0
  96. package/out/download/__next._full.txt +34 -0
  97. package/out/download/__next._head.txt +5 -0
  98. package/out/download/__next._index.txt +7 -0
  99. package/out/download/__next._tree.txt +4 -0
  100. package/out/download/__next.download.__PAGE__.txt +16 -0
  101. package/out/download/__next.download.txt +5 -0
  102. package/out/download/index.html +15 -0
  103. package/out/download/index.txt +34 -0
  104. package/out/favicon.ico +0 -0
  105. package/out/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  106. package/out/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
  107. package/out/fonts/jetbrains-mono-latin-600-normal.woff2 +0 -0
  108. package/out/fonts/jetbrains-mono-latin-700-normal.woff2 +0 -0
  109. package/out/index.html +15 -0
  110. package/out/index.txt +21 -0
  111. package/out/lottery/__next._full.txt +21 -0
  112. package/out/lottery/__next._head.txt +5 -0
  113. package/out/lottery/__next._index.txt +7 -0
  114. package/out/lottery/__next._tree.txt +3 -0
  115. package/out/lottery/__next.lottery.__PAGE__.txt +9 -0
  116. package/out/lottery/__next.lottery.txt +6 -0
  117. package/out/lottery/index.html +15 -0
  118. package/out/lottery/index.txt +21 -0
  119. package/out/ping/__next._full.txt +21 -0
  120. package/out/ping/__next._head.txt +5 -0
  121. package/out/ping/__next._index.txt +7 -0
  122. package/out/ping/__next._tree.txt +4 -0
  123. package/out/ping/__next.ping.__PAGE__.txt +10 -0
  124. package/out/ping/__next.ping.txt +5 -0
  125. package/out/ping/index.html +15 -0
  126. package/out/ping/index.txt +21 -0
  127. package/out/pwa-512x512.png +0 -0
  128. package/out/web3/__next._full.txt +21 -0
  129. package/out/web3/__next._head.txt +5 -0
  130. package/out/web3/__next._index.txt +7 -0
  131. package/out/web3/__next._tree.txt +3 -0
  132. package/out/web3/__next.web3.__PAGE__.txt +9 -0
  133. package/out/web3/__next.web3.txt +6 -0
  134. package/out/web3/ed25519/__next._full.txt +20 -0
  135. package/out/web3/ed25519/__next._head.txt +5 -0
  136. package/out/web3/ed25519/__next._index.txt +7 -0
  137. package/out/web3/ed25519/__next._tree.txt +3 -0
  138. package/out/web3/ed25519/__next.web3.ed25519.__PAGE__.txt +6 -0
  139. package/out/web3/ed25519/__next.web3.ed25519.txt +5 -0
  140. package/out/web3/ed25519/__next.web3.txt +6 -0
  141. package/out/web3/ed25519/index.html +1 -0
  142. package/out/web3/ed25519/index.txt +20 -0
  143. package/out/web3/index.html +15 -0
  144. package/out/web3/index.txt +21 -0
  145. package/out/web3/tools/__next._full.txt +20 -0
  146. package/out/web3/tools/__next._head.txt +5 -0
  147. package/out/web3/tools/__next._index.txt +7 -0
  148. package/out/web3/tools/__next._tree.txt +3 -0
  149. package/out/web3/tools/__next.web3.tools.__PAGE__.txt +6 -0
  150. package/out/web3/tools/__next.web3.tools.txt +5 -0
  151. package/out/web3/tools/__next.web3.txt +6 -0
  152. package/out/web3/tools/index.html +1 -0
  153. package/out/web3/tools/index.txt +20 -0
  154. package/package.json +162 -48
  155. package/public/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  156. package/public/fonts/jetbrains-mono-latin-500-normal.woff2 +0 -0
  157. package/public/fonts/jetbrains-mono-latin-600-normal.woff2 +0 -0
  158. package/public/fonts/jetbrains-mono-latin-700-normal.woff2 +0 -0
  159. package/public/pwa-512x512.png +0 -0
  160. package/server/cli.js +3 -0
  161. package/server/index.js +963 -0
  162. package/{src → server/src}/config.js +51 -39
  163. package/{src → server/src}/core/cid.js +157 -146
  164. package/{src → server/src}/index.js +1950 -1201
  165. package/server/src/utils/api.js +68 -0
  166. package/server/src/utils/avatar.js +11 -0
  167. package/{src → server/src}/utils/errors.js +70 -66
  168. package/server/src/utils/mostWallet.js +42 -0
  169. package/server/src/utils/mp.js +117 -0
  170. package/{src → server/src}/utils/security.js +173 -169
  171. package/server/src/utils/userIdentity.js +93 -0
  172. package/build.mjs +0 -40
  173. package/cli.js +0 -2
  174. package/public/app.css +0 -1519
  175. package/public/app.jsx +0 -1543
  176. package/public/bundle.css +0 -1
  177. package/public/bundle.js +0 -107
  178. package/public/error-boundary.jsx +0 -50
  179. package/public/index.html +0 -16
  180. package/public/index.jsx +0 -20
  181. 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 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/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
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- import './server.js';