most-box 0.1.1 → 0.1.2

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 (215) hide show
  1. package/README.md +21 -14
  2. package/electron/main.js +72 -8
  3. package/electron/preload.js +6 -0
  4. package/out/404/index.html +2 -2
  5. package/out/404.html +2 -2
  6. package/out/__next.__PAGE__.txt +6 -6
  7. package/out/__next._full.txt +16 -16
  8. package/out/__next._head.txt +3 -3
  9. package/out/__next._index.txt +8 -8
  10. package/out/__next._tree.txt +5 -5
  11. package/out/_next/static/chunks/0.e2avjgna_b2.js +1 -0
  12. package/out/_next/static/chunks/{0ho~log~~-jwp.css → 03h~nhgj0hv3p.css} +1 -1
  13. package/out/_next/static/chunks/07td.jq7xff84.css +1 -0
  14. package/out/_next/static/chunks/{0qou.u2e2dy48.css → 0adx~d-j05c9d.css} +2 -2
  15. package/out/_next/static/chunks/0aq.rc9woa2nz.js +1 -0
  16. package/out/_next/static/chunks/0etes81d_cihn.js +1 -0
  17. package/out/_next/static/chunks/0g_a~e050bgzg.css +1 -0
  18. package/out/_next/static/chunks/{0o6lrkxy4jwag.js → 0gcsdf57gcm6h.js} +1 -1
  19. package/out/_next/static/chunks/0gwian.hp3-92.js +1 -0
  20. package/out/_next/static/chunks/0hpev4am9jpmu.css +1 -0
  21. package/out/_next/static/chunks/0l5_.uqb-uqb8.js +1 -0
  22. package/out/_next/static/chunks/0mex8svsiv-2l.js +1 -0
  23. package/out/_next/static/chunks/0myq9gs8szydh.js +1 -0
  24. package/out/_next/static/chunks/{0usvo~vu7r8np.js → 0o9ce4cyf76by.js} +1 -1
  25. package/out/_next/static/chunks/0p0sv~fuddvgr.js +1 -0
  26. package/out/_next/static/chunks/{0o98f1yq..o.8.js → 0pt.5cg1t09qs.js} +1 -1
  27. package/out/_next/static/chunks/0q0ksgxg98xgd.js +1 -0
  28. package/out/_next/static/chunks/0qgx9t4jx16ua.css +1 -0
  29. package/out/_next/static/chunks/0ukyg~tkm~h2m.css +1 -0
  30. package/out/_next/static/chunks/0wtf0xsiicxx6.js +1 -0
  31. package/out/_next/static/chunks/0xdwau5k2augv.css +4 -0
  32. package/out/_next/static/chunks/12nr19.nnn6s3.js +5 -0
  33. package/out/_next/static/chunks/{0qub_r0x_r-e9.css → 12pep-2t-qg4n.css} +1 -1
  34. package/out/_next/static/chunks/14_inksek_rth.js +2 -0
  35. package/out/_next/static/chunks/153-sz7s.qml2.js +1 -0
  36. package/out/_next/static/chunks/16xls5tt_68lx.js +1 -0
  37. package/out/_next/static/chunks/{turbopack-0xs6mybc~5t_3.js → turbopack-0xta0kqwzkf28.js} +1 -1
  38. package/out/_not-found/__next._full.txt +13 -13
  39. package/out/_not-found/__next._head.txt +3 -3
  40. package/out/_not-found/__next._index.txt +8 -8
  41. package/out/_not-found/__next._not-found.__PAGE__.txt +4 -4
  42. package/out/_not-found/__next._not-found.txt +3 -3
  43. package/out/_not-found/__next._tree.txt +3 -3
  44. package/out/_not-found/index.html +2 -2
  45. package/out/_not-found/index.txt +13 -13
  46. package/out/admin/__next._full.txt +15 -15
  47. package/out/admin/__next._head.txt +3 -3
  48. package/out/admin/__next._index.txt +8 -8
  49. package/out/admin/__next._tree.txt +4 -4
  50. package/out/admin/__next.admin.__PAGE__.txt +4 -4
  51. package/out/admin/__next.admin.txt +4 -4
  52. package/out/admin/index.html +2 -2
  53. package/out/admin/index.txt +15 -15
  54. package/out/app/__next._full.txt +14 -14
  55. package/out/app/__next._head.txt +3 -3
  56. package/out/app/__next._index.txt +8 -8
  57. package/out/app/__next._tree.txt +3 -3
  58. package/out/app/__next.app.__PAGE__.txt +4 -4
  59. package/out/app/__next.app.txt +3 -3
  60. package/out/app/index.html +2 -2
  61. package/out/app/index.txt +14 -14
  62. package/out/chat/__next._full.txt +15 -15
  63. package/out/chat/__next._head.txt +3 -3
  64. package/out/chat/__next._index.txt +8 -8
  65. package/out/chat/__next._tree.txt +4 -4
  66. package/out/chat/__next.chat.__PAGE__.txt +4 -4
  67. package/out/chat/__next.chat.txt +4 -4
  68. package/out/chat/index.html +2 -2
  69. package/out/chat/index.txt +15 -15
  70. package/out/chat/join/__next._full.txt +25 -0
  71. package/out/chat/join/__next._head.txt +5 -0
  72. package/out/{changelog → chat/join}/__next._index.txt +8 -8
  73. package/out/chat/join/__next._tree.txt +5 -0
  74. package/out/chat/join/__next.chat.join.__PAGE__.txt +9 -0
  75. package/out/chat/join/__next.chat.join.txt +5 -0
  76. package/out/chat/join/__next.chat.txt +5 -0
  77. package/out/chat/join/index.html +15 -0
  78. package/out/chat/join/index.txt +25 -0
  79. package/out/download/__next._full.txt +21 -21
  80. package/out/download/__next._head.txt +3 -3
  81. package/out/download/__next._index.txt +8 -8
  82. package/out/download/__next._tree.txt +5 -5
  83. package/out/download/__next.download.__PAGE__.txt +9 -9
  84. package/out/download/__next.download.txt +3 -3
  85. package/out/download/index.html +2 -2
  86. package/out/download/index.txt +21 -21
  87. package/out/index.html +2 -2
  88. package/out/index.txt +16 -16
  89. package/out/note/__next._full.txt +14 -14
  90. package/out/note/__next._head.txt +3 -3
  91. package/out/note/__next._index.txt +8 -8
  92. package/out/note/__next._tree.txt +3 -3
  93. package/out/note/__next.note.__PAGE__.txt +4 -4
  94. package/out/note/__next.note.txt +3 -3
  95. package/out/note/index.html +2 -2
  96. package/out/note/index.txt +14 -14
  97. package/out/ping/__next._full.txt +16 -16
  98. package/out/ping/__next._head.txt +3 -3
  99. package/out/ping/__next._index.txt +8 -8
  100. package/out/ping/__next._tree.txt +5 -5
  101. package/out/ping/__next.ping.__PAGE__.txt +5 -5
  102. package/out/ping/__next.ping.txt +4 -4
  103. package/out/ping/index.html +2 -2
  104. package/out/ping/index.txt +16 -16
  105. package/out/web3/__next._full.txt +15 -15
  106. package/out/web3/__next._head.txt +3 -3
  107. package/out/web3/__next._index.txt +8 -8
  108. package/out/web3/__next._tree.txt +4 -4
  109. package/out/web3/__next.web3.__PAGE__.txt +4 -4
  110. package/out/web3/__next.web3.txt +4 -4
  111. package/out/web3/ed25519/__next._full.txt +13 -13
  112. package/out/web3/ed25519/__next._head.txt +3 -3
  113. package/out/web3/ed25519/__next._index.txt +8 -8
  114. package/out/web3/ed25519/__next._tree.txt +4 -4
  115. package/out/web3/ed25519/__next.web3.ed25519.__PAGE__.txt +2 -2
  116. package/out/web3/ed25519/__next.web3.ed25519.txt +3 -3
  117. package/out/web3/ed25519/__next.web3.txt +4 -4
  118. package/out/web3/ed25519/index.html +1 -1
  119. package/out/web3/ed25519/index.txt +13 -13
  120. package/out/web3/index.html +2 -2
  121. package/out/web3/index.txt +15 -15
  122. package/out/web3/tools/__next._full.txt +13 -13
  123. package/out/web3/tools/__next._head.txt +3 -3
  124. package/out/web3/tools/__next._index.txt +8 -8
  125. package/out/web3/tools/__next._tree.txt +4 -4
  126. package/out/web3/tools/__next.web3.tools.__PAGE__.txt +2 -2
  127. package/out/web3/tools/__next.web3.tools.txt +3 -3
  128. package/out/web3/tools/__next.web3.txt +4 -4
  129. package/out/web3/tools/index.html +1 -1
  130. package/out/web3/tools/index.txt +13 -13
  131. package/package.json +19 -9
  132. package/server/index.js +133 -1303
  133. package/server/src/config.js +1 -1
  134. package/server/src/core/channelAttachment.js +68 -0
  135. package/server/src/core/cid.js +2 -88
  136. package/server/src/core/cidTopic.js +29 -0
  137. package/server/src/core/mostLink.js +88 -0
  138. package/server/src/http/access.js +123 -0
  139. package/server/src/http/app.js +1095 -0
  140. package/server/src/http/errors.js +35 -0
  141. package/server/src/http/nodeLogs.js +53 -0
  142. package/server/src/http/nodeStatus.js +146 -0
  143. package/server/src/http/staticFiles.js +84 -0
  144. package/server/src/http/uploads.js +114 -0
  145. package/server/src/index.js +799 -211
  146. package/server/src/node/config.js +38 -6
  147. package/server/src/utils/api.js +287 -14
  148. package/server/src/utils/auth.js +63 -0
  149. package/server/src/utils/dateTime.js +30 -0
  150. package/server/src/utils/downloadMessages.js +89 -0
  151. package/server/src/utils/errors.js +7 -0
  152. package/server/src/utils/mostWallet.js +151 -0
  153. package/server/src/utils/mp.js +2 -26
  154. package/server/src/utils/noteBackup.js +2 -5
  155. package/server/src/utils/noteUtils.js +11 -3
  156. package/server/src/utils/userIdentity.js +0 -1
  157. package/out/_next/static/chunks/00-u5nq76f0.j.js +0 -1
  158. package/out/_next/static/chunks/00fm8lijienf1.js +0 -1
  159. package/out/_next/static/chunks/00o9ht.f2qm00.css +0 -4
  160. package/out/_next/static/chunks/00zi-erhjrny2.js +0 -2
  161. package/out/_next/static/chunks/084xf0edl9sfo.js +0 -1
  162. package/out/_next/static/chunks/09f1gfke9m5wg.css +0 -1
  163. package/out/_next/static/chunks/09xyi6fpro_d-.css +0 -1
  164. package/out/_next/static/chunks/0_npg_pcoywti.js +0 -5
  165. package/out/_next/static/chunks/0_r_mk1~6bosc.js +0 -1
  166. package/out/_next/static/chunks/0arm0a6adt7cc.css +0 -1
  167. package/out/_next/static/chunks/0c9j3eq_14vv2.css +0 -1
  168. package/out/_next/static/chunks/0d4bueddmcnca.js +0 -1
  169. package/out/_next/static/chunks/0gtwvy1z9ksa7.css +0 -1
  170. package/out/_next/static/chunks/0j27tcmtt4ly7.js +0 -1
  171. package/out/_next/static/chunks/0j3v4mq67wtnh.js +0 -1
  172. package/out/_next/static/chunks/0lkmf5ry.s_7w.js +0 -1
  173. package/out/_next/static/chunks/0p486m03-zfoi.js +0 -1
  174. package/out/_next/static/chunks/0r1~k82nji8sf.js +0 -1
  175. package/out/_next/static/chunks/0v7qp4hv-_._r.js +0 -1
  176. package/out/_next/static/chunks/0wuwlgcn6gxqt.js +0 -1
  177. package/out/_next/static/chunks/0xl5_avhu._i8.js +0 -1
  178. package/out/_next/static/chunks/10kvl8vj_plm-.js +0 -1
  179. package/out/_next/static/chunks/16m27azcs4k6w.js +0 -1
  180. package/out/changelog/__next._full.txt +0 -25
  181. package/out/changelog/__next._head.txt +0 -5
  182. package/out/changelog/__next._tree.txt +0 -5
  183. package/out/changelog/__next.changelog.__PAGE__.txt +0 -10
  184. package/out/changelog/__next.changelog.txt +0 -5
  185. package/out/changelog/index.html +0 -15
  186. package/out/changelog/index.txt +0 -25
  187. package/out/docs/__next._full.txt +0 -25
  188. package/out/docs/__next._head.txt +0 -5
  189. package/out/docs/__next._index.txt +0 -9
  190. package/out/docs/__next._tree.txt +0 -5
  191. package/out/docs/__next.docs.__PAGE__.txt +0 -10
  192. package/out/docs/__next.docs.txt +0 -5
  193. package/out/docs/getting-started/__next._full.txt +0 -25
  194. package/out/docs/getting-started/__next._head.txt +0 -5
  195. package/out/docs/getting-started/__next._index.txt +0 -9
  196. package/out/docs/getting-started/__next._tree.txt +0 -5
  197. package/out/docs/getting-started/__next.docs.getting-started.__PAGE__.txt +0 -10
  198. package/out/docs/getting-started/__next.docs.getting-started.txt +0 -5
  199. package/out/docs/getting-started/__next.docs.txt +0 -5
  200. package/out/docs/getting-started/index.html +0 -15
  201. package/out/docs/getting-started/index.txt +0 -25
  202. package/out/docs/index.html +0 -15
  203. package/out/docs/index.txt +0 -25
  204. package/out/note/edit/__next._full.txt +0 -24
  205. package/out/note/edit/__next._head.txt +0 -5
  206. package/out/note/edit/__next._index.txt +0 -9
  207. package/out/note/edit/__next._tree.txt +0 -4
  208. package/out/note/edit/__next.note.edit.__PAGE__.txt +0 -9
  209. package/out/note/edit/__next.note.edit.txt +0 -5
  210. package/out/note/edit/__next.note.txt +0 -5
  211. package/out/note/edit/index.html +0 -15
  212. package/out/note/edit/index.txt +0 -24
  213. /package/out/_next/static/{sIuUKxnnGU7K9Tu9UDKE8 → t7ZIeQpVvjz4a7-5Tt-VK}/_buildManifest.js +0 -0
  214. /package/out/_next/static/{sIuUKxnnGU7K9Tu9UDKE8 → t7ZIeQpVvjz4a7-5Tt-VK}/_clientMiddlewareManifest.js +0 -0
  215. /package/out/_next/static/{sIuUKxnnGU7K9Tu9UDKE8 → t7ZIeQpVvjz4a7-5Tt-VK}/_ssgManifest.js +0 -0
@@ -0,0 +1,35 @@
1
+ export function getApiErrorStatus(err) {
2
+ switch (err.code) {
3
+ case 'VALIDATION_ERROR':
4
+ case 'PATH_SECURITY_ERROR':
5
+ case 'FILE_SIZE_ERROR':
6
+ return 400
7
+ case 'PEER_NOT_FOUND':
8
+ return 503
9
+ case 'INTEGRITY_ERROR':
10
+ return 422
11
+ case 'CONFLICT':
12
+ return 409
13
+ case 'PERMISSION_ERROR':
14
+ return 403
15
+ case 'ENGINE_NOT_INITIALIZED':
16
+ return 503
17
+ default:
18
+ return 500
19
+ }
20
+ }
21
+
22
+ export function errorJson(c, err) {
23
+ return c.json(
24
+ {
25
+ error: err.message,
26
+ code: err.code || 'UNKNOWN',
27
+ },
28
+ getApiErrorStatus(err)
29
+ )
30
+ }
31
+
32
+ export function badRequestOrAppError(c, err) {
33
+ if (err.code) return errorJson(c, err)
34
+ return c.json({ error: err.message }, 400)
35
+ }
@@ -0,0 +1,53 @@
1
+ const NODE_LOG_FILTERS = {
2
+ join: ['join', 'joined', 'topic'],
3
+ pull: ['pull', 'p2p'],
4
+ verify: ['verify', 'verified', 'integrity', 'download:success'],
5
+ serve: ['seed', 'seeding', 'holding', 'publish:success', 'topic:joined'],
6
+ error: ['error', 'failed', 'fail'],
7
+ }
8
+
9
+ function getNodeLogSearchText(log = {}) {
10
+ let dataText = ''
11
+ try {
12
+ dataText = JSON.stringify(log.data || {})
13
+ } catch {}
14
+
15
+ return [log.level, log.event, log.message, dataText]
16
+ .map(value => String(value || '').toLowerCase())
17
+ .join(' ')
18
+ }
19
+
20
+ function matchesNodeLogFilter(log, filter) {
21
+ const normalized = String(filter || 'all')
22
+ .trim()
23
+ .toLowerCase()
24
+ if (!normalized || normalized === 'all') return true
25
+
26
+ const text = getNodeLogSearchText(log)
27
+ if (normalized === 'error') {
28
+ return (
29
+ log.level === 'error' ||
30
+ NODE_LOG_FILTERS.error.some(term => text.includes(term))
31
+ )
32
+ }
33
+
34
+ const terms = NODE_LOG_FILTERS[normalized] || [normalized]
35
+ return terms.some(term => text.includes(term))
36
+ }
37
+
38
+ export function listFilteredNodeLogs(nodeLogger, options = {}) {
39
+ const limit = Math.max(1, Math.min(Number(options.limit) || 100, 1000))
40
+ const filter = String(options.filter || 'all')
41
+ .trim()
42
+ .toLowerCase()
43
+ const query = String(options.query || '')
44
+ .trim()
45
+ .toLowerCase()
46
+ const logs = nodeLogger
47
+ .list(1000)
48
+ .filter(log => matchesNodeLogFilter(log, filter))
49
+ .filter(log => !query || getNodeLogSearchText(log).includes(query))
50
+ .slice(0, limit)
51
+
52
+ return { filter, query, logs }
53
+ }
@@ -0,0 +1,146 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { DEFAULT_NODE_HOST } from '../node/config.js'
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
7
+ const PACKAGE_JSON = readPackageJson()
8
+
9
+ function readPackageJson() {
10
+ try {
11
+ return JSON.parse(
12
+ fs.readFileSync(
13
+ path.join(__dirname, '..', '..', '..', 'package.json'),
14
+ 'utf-8'
15
+ )
16
+ )
17
+ } catch {
18
+ return { version: '0.0.0' }
19
+ }
20
+ }
21
+
22
+ export function getNetworkAddresses(appPort) {
23
+ return {
24
+ port: appPort,
25
+ addresses: [
26
+ { type: 'local', ip: 'localhost', label: '本机', iface: 'loopback' },
27
+ ],
28
+ }
29
+ }
30
+
31
+ export async function buildNodeStatus(engine, configStore, appPort) {
32
+ const config = configStore.getNodeConfig()
33
+ const { remoteInvites, ...publicConfig } = config
34
+ const remoteInviteCount = remoteInvites.length
35
+ const storage = await engine.getStorageStats()
36
+ const network = engine.getNetworkStatus()
37
+ const holdings = engine.listHoldings()
38
+
39
+ return {
40
+ status: 'online',
41
+ version: PACKAGE_JSON.version,
42
+ uptimeSeconds: Math.floor(process.uptime()),
43
+ nodeId: engine.getNodeId(),
44
+ host: DEFAULT_NODE_HOST,
45
+ port: appPort,
46
+ listen: getNetworkAddresses(appPort),
47
+ dataPath: configStore.getDataPath(),
48
+ config: {
49
+ ...publicConfig,
50
+ remoteInviteCount,
51
+ remoteInviteConfigured: remoteInviteCount > 0,
52
+ },
53
+ policy: {
54
+ maxFileSizeBytes: config.maxFileSizeBytes,
55
+ },
56
+ capacity: {
57
+ configuredBytes: config.capacityBytes,
58
+ usedBytes: storage.used,
59
+ freeBytes: Math.max(0, config.capacityBytes - storage.used),
60
+ },
61
+ storage,
62
+ network,
63
+ holdings,
64
+ }
65
+ }
66
+
67
+ export function buildOpenApiSpec(appPort) {
68
+ return {
69
+ openapi: '3.1.0',
70
+ info: {
71
+ title: 'MostBox Node Daemon API',
72
+ version: PACKAGE_JSON.version,
73
+ },
74
+ servers: [{ url: `http://localhost:${appPort}` }],
75
+ paths: {
76
+ '/api/node/status': {
77
+ get: {
78
+ summary: 'Get node daemon status',
79
+ responses: { 200: { description: 'Node status' } },
80
+ },
81
+ },
82
+ '/api/node/config': {
83
+ get: {
84
+ summary: 'Get node daemon config',
85
+ responses: { 200: { description: 'Node config' } },
86
+ },
87
+ post: {
88
+ summary: 'Update node daemon config',
89
+ responses: { 200: { description: 'Updated config' } },
90
+ },
91
+ },
92
+ '/api/node/policy': {
93
+ get: {
94
+ summary: 'Get local storage limits',
95
+ responses: { 200: { description: 'Storage limits' } },
96
+ },
97
+ post: {
98
+ summary: 'Update local storage limits',
99
+ responses: { 200: { description: 'Updated policy' } },
100
+ },
101
+ },
102
+ '/api/node/policy/evaluate': {
103
+ post: {
104
+ summary: 'Evaluate a local file against storage limits',
105
+ responses: { 200: { description: 'Storage limit decision' } },
106
+ },
107
+ },
108
+ '/api/node/holdings': {
109
+ get: {
110
+ summary: 'List CID replicas held by this node',
111
+ responses: { 200: { description: 'Node holdings' } },
112
+ },
113
+ post: {
114
+ summary: 'Add a held CID replica record and join its topic',
115
+ responses: { 200: { description: 'Created holding' } },
116
+ },
117
+ },
118
+ '/api/node/logs': {
119
+ get: {
120
+ summary: 'Read recent node daemon logs',
121
+ responses: { 200: { description: 'Node logs' } },
122
+ },
123
+ delete: {
124
+ summary: 'Clear node daemon logs',
125
+ responses: { 200: { description: 'Logs cleared' } },
126
+ },
127
+ },
128
+ '/api/node/diagnostics': {
129
+ get: {
130
+ summary: 'Export node diagnostics snapshot',
131
+ responses: { 200: { description: 'Diagnostics snapshot' } },
132
+ },
133
+ },
134
+ '/api/p2p/pull': {
135
+ post: {
136
+ summary: 'Pull a full file replica by CID',
137
+ responses: { 200: { description: 'Pull task result' } },
138
+ },
139
+ },
140
+ },
141
+ }
142
+ }
143
+
144
+ export function getPackageVersion() {
145
+ return PACKAGE_JSON.version
146
+ }
@@ -0,0 +1,84 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { serveStatic } from '@hono/node-server/serve-static'
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
7
+
8
+ const MIME_TYPES = {
9
+ '.html': 'text/html; charset=utf-8',
10
+ '.js': 'application/javascript; charset=utf-8',
11
+ '.css': 'text/css; charset=utf-8',
12
+ '.json': 'application/json',
13
+ '.png': 'image/png',
14
+ '.jpg': 'image/jpeg',
15
+ '.jpeg': 'image/jpeg',
16
+ '.gif': 'image/gif',
17
+ '.webp': 'image/webp',
18
+ '.svg': 'image/svg+xml',
19
+ '.ico': 'image/x-icon',
20
+ '.mp4': 'video/mp4',
21
+ '.webm': 'video/webm',
22
+ '.ogg': 'video/ogg',
23
+ '.mp3': 'audio/mpeg',
24
+ '.wav': 'audio/wav',
25
+ '.flac': 'audio/flac',
26
+ '.aac': 'audio/aac',
27
+ '.m4a': 'audio/mp4',
28
+ '.opus': 'audio/opus',
29
+ '.woff2': 'font/woff2',
30
+ '.woff': 'font/woff',
31
+ }
32
+
33
+ export function getMimeType(fileName) {
34
+ const ext = path.extname(fileName).toLowerCase()
35
+ return MIME_TYPES[ext] || 'application/octet-stream'
36
+ }
37
+
38
+ export function registerStaticRoutes(app) {
39
+ const publicDir = path.join(__dirname, '..', '..', '..', 'out')
40
+
41
+ app.get('/static/*', serveStatic({ root: './out' }))
42
+ app.get('/_next/*', serveStatic({ root: './out' }))
43
+
44
+ app.all('/api/*', c => {
45
+ return c.json({ error: 'Not found' }, 404)
46
+ })
47
+
48
+ app.get('*', async c => {
49
+ const pathname = c.req.path
50
+ const filePath = path.join(publicDir, pathname)
51
+ const resolved = path.resolve(filePath)
52
+ const resolvedPublic = path.resolve(publicDir)
53
+
54
+ if (
55
+ !resolved.startsWith(resolvedPublic + path.sep) &&
56
+ resolved !== resolvedPublic
57
+ ) {
58
+ return c.json({ error: 'Not found' }, 404)
59
+ }
60
+
61
+ if (fs.existsSync(filePath)) {
62
+ const stat = fs.statSync(filePath)
63
+ if (stat.isFile()) {
64
+ c.header('Content-Type', getMimeType(filePath))
65
+ return c.body(fs.readFileSync(filePath))
66
+ }
67
+ if (stat.isDirectory()) {
68
+ const dirIndex = path.join(filePath, 'index.html')
69
+ if (fs.existsSync(dirIndex)) {
70
+ c.header('Content-Type', 'text/html; charset=utf-8')
71
+ return c.body(fs.readFileSync(dirIndex, 'utf-8'))
72
+ }
73
+ }
74
+ }
75
+
76
+ const indexPath = path.join(publicDir, 'index.html')
77
+ if (fs.existsSync(indexPath)) {
78
+ c.header('Content-Type', 'text/html; charset=utf-8')
79
+ return c.body(fs.readFileSync(indexPath, 'utf-8'))
80
+ }
81
+
82
+ return c.json({ error: 'Not found' }, 404)
83
+ })
84
+ }
@@ -0,0 +1,114 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import os from 'node:os'
4
+ import Busboy from 'busboy'
5
+ import { MAX_FILE_SIZE } from '../config.js'
6
+
7
+ export const UPLOAD_TMP_DIR = path.join(os.tmpdir(), 'most-box-uploads')
8
+
9
+ function decodeFilenameFromHeader(headerStr) {
10
+ if (!headerStr) return null
11
+
12
+ const filenameStarMatch = headerStr.match(
13
+ /filename\*=(?:UTF-8''|utf-8'')([^;\r\n]+)/i
14
+ )
15
+ if (filenameStarMatch) {
16
+ return decodeURIComponent(filenameStarMatch[1])
17
+ }
18
+
19
+ const filenameMatch = headerStr.match(/filename="([^"]+)"/)
20
+ if (filenameMatch) {
21
+ const rawFilename = filenameMatch[1]
22
+ try {
23
+ const buf = Buffer.from(rawFilename, 'latin1')
24
+ const decoded = buf.toString('utf8')
25
+ if (decoded.includes('\ufffd')) {
26
+ return rawFilename
27
+ }
28
+ return decoded
29
+ } catch {
30
+ return rawFilename
31
+ }
32
+ }
33
+
34
+ const filenamePlainMatch = headerStr.match(/filename=([^;\r\n]+)/)
35
+ if (filenamePlainMatch) {
36
+ return filenamePlainMatch[1].trim()
37
+ }
38
+ return null
39
+ }
40
+
41
+ export async function parseMultipartBusboy(req, maxUploadSize = MAX_FILE_SIZE) {
42
+ return new Promise((resolve, reject) => {
43
+ if (!fs.existsSync(UPLOAD_TMP_DIR)) {
44
+ fs.mkdirSync(UPLOAD_TMP_DIR, { recursive: true })
45
+ }
46
+
47
+ const busboy = Busboy({
48
+ headers: req.headers,
49
+ limits: {
50
+ fileSize: maxUploadSize,
51
+ files: 1,
52
+ fields: 0,
53
+ },
54
+ })
55
+
56
+ const result = { filePath: null, filename: null }
57
+ let fileSize = 0
58
+ let writeStream = null
59
+ let tempPath = null
60
+
61
+ busboy.on('file', (name, stream, info) => {
62
+ result.filename = decodeFilenameFromHeader(`filename="${info.filename}"`)
63
+ tempPath = path.join(
64
+ UPLOAD_TMP_DIR,
65
+ `upload_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
66
+ )
67
+ writeStream = fs.createWriteStream(tempPath)
68
+
69
+ stream.on('data', chunk => {
70
+ fileSize += chunk.length
71
+ if (fileSize > maxUploadSize) {
72
+ stream.destroy()
73
+ writeStream.destroy()
74
+ fs.unlink(tempPath, () => {})
75
+ reject(new Error('File too large'))
76
+ return
77
+ }
78
+ })
79
+
80
+ stream.on('error', () => {
81
+ if (tempPath) fs.unlink(tempPath, () => {})
82
+ })
83
+
84
+ stream.pipe(writeStream)
85
+
86
+ writeStream.on('finish', () => {
87
+ result.filePath = tempPath
88
+ resolve(result)
89
+ })
90
+
91
+ writeStream.on('error', err => {
92
+ if (tempPath) fs.unlink(tempPath, () => {})
93
+ reject(err)
94
+ })
95
+ })
96
+
97
+ busboy.on('error', err => {
98
+ if (tempPath) fs.unlink(tempPath, () => {})
99
+ reject(err)
100
+ })
101
+
102
+ busboy.on('close', () => {
103
+ if (!result.filename) {
104
+ resolve(null)
105
+ }
106
+ })
107
+
108
+ req.on('error', err => {
109
+ if (tempPath) fs.unlink(tempPath, () => {})
110
+ reject(err)
111
+ })
112
+ req.pipe(busboy)
113
+ })
114
+ }