most-box 0.1.1 → 0.1.3
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/README.md +39 -19
- package/electron/afterPack.cjs +87 -0
- package/electron/main.js +156 -8
- package/electron/preload.js +6 -0
- package/electron/updateChecker.js +97 -0
- package/electron/updateChecker.test.js +147 -0
- package/out/404/index.html +2 -2
- package/out/404.html +2 -2
- package/out/__next.__PAGE__.txt +6 -6
- package/out/__next._full.txt +16 -16
- package/out/__next._head.txt +3 -3
- package/out/__next._index.txt +8 -8
- package/out/__next._tree.txt +5 -5
- package/out/_next/static/chunks/04mo7rr..0_1q.js +1 -0
- package/out/_next/static/chunks/06rf3qq5ggs6v.js +1 -0
- package/out/_next/static/chunks/07td.jq7xff84.css +1 -0
- package/out/_next/static/chunks/0_0oph_z1az14.js +1 -0
- package/out/_next/static/chunks/{0qou.u2e2dy48.css → 0adx~d-j05c9d.css} +2 -2
- package/out/_next/static/chunks/0cl7d~7abnk_p.css +1 -0
- package/out/_next/static/chunks/0d306t1wvjpdx.js +1 -0
- package/out/_next/static/chunks/0g_a~e050bgzg.css +1 -0
- package/out/_next/static/chunks/{0o6lrkxy4jwag.js → 0gcsdf57gcm6h.js} +1 -1
- package/out/_next/static/chunks/0hpev4am9jpmu.css +1 -0
- package/out/_next/static/chunks/0m_5nb6x8qy._.js +1 -0
- package/out/_next/static/chunks/0n.ayxmsar6e5.js +1 -0
- package/out/_next/static/chunks/{0usvo~vu7r8np.js → 0o9ce4cyf76by.js} +1 -1
- package/out/_next/static/chunks/0olqjomda37-e.js +1 -0
- package/out/_next/static/chunks/{0o98f1yq..o.8.js → 0pt.5cg1t09qs.js} +1 -1
- package/out/_next/static/chunks/0qgx9t4jx16ua.css +1 -0
- package/out/_next/static/chunks/0s~g.l~x049o2.js +1 -0
- package/out/_next/static/chunks/0ukyg~tkm~h2m.css +1 -0
- package/out/_next/static/chunks/0voe1.ttrh84k.css +1 -0
- package/out/_next/static/chunks/0wtf0xsiicxx6.js +1 -0
- package/out/_next/static/chunks/0x.ky97owcxxs.js +1 -0
- package/out/_next/static/chunks/0xdwau5k2augv.css +4 -0
- package/out/_next/static/chunks/0ysj5b94vu4ri.js +1 -0
- package/out/_next/static/chunks/12nr19.nnn6s3.js +5 -0
- package/out/_next/static/chunks/{0qub_r0x_r-e9.css → 12pep-2t-qg4n.css} +1 -1
- package/out/_next/static/chunks/14_inksek_rth.js +2 -0
- package/out/_next/static/chunks/153-sz7s.qml2.js +1 -0
- package/out/_next/static/chunks/17cwkb2yn_akx.js +1 -0
- package/out/_next/static/chunks/184hxsuf-5c84.js +1 -0
- package/out/_next/static/chunks/{turbopack-0xs6mybc~5t_3.js → turbopack-0xta0kqwzkf28.js} +1 -1
- package/out/_not-found/__next._full.txt +13 -13
- package/out/_not-found/__next._head.txt +3 -3
- package/out/_not-found/__next._index.txt +8 -8
- package/out/_not-found/__next._not-found.__PAGE__.txt +4 -4
- package/out/_not-found/__next._not-found.txt +3 -3
- package/out/_not-found/__next._tree.txt +3 -3
- package/out/_not-found/index.html +2 -2
- package/out/_not-found/index.txt +13 -13
- package/out/admin/__next._full.txt +15 -15
- package/out/admin/__next._head.txt +3 -3
- package/out/admin/__next._index.txt +8 -8
- package/out/admin/__next._tree.txt +4 -4
- package/out/admin/__next.admin.__PAGE__.txt +4 -4
- package/out/admin/__next.admin.txt +4 -4
- package/out/admin/index.html +2 -2
- package/out/admin/index.txt +15 -15
- package/out/app/__next._full.txt +14 -14
- package/out/app/__next._head.txt +3 -3
- package/out/app/__next._index.txt +8 -8
- package/out/app/__next._tree.txt +3 -3
- package/out/app/__next.app.__PAGE__.txt +4 -4
- package/out/app/__next.app.txt +3 -3
- package/out/app/index.html +2 -2
- package/out/app/index.txt +14 -14
- package/out/chat/__next._full.txt +15 -15
- package/out/chat/__next._head.txt +3 -3
- package/out/chat/__next._index.txt +8 -8
- package/out/chat/__next._tree.txt +4 -4
- package/out/chat/__next.chat.__PAGE__.txt +4 -4
- package/out/chat/__next.chat.txt +4 -4
- package/out/chat/index.html +2 -2
- package/out/chat/index.txt +15 -15
- package/out/chat/join/__next._full.txt +25 -0
- package/out/chat/join/__next._head.txt +5 -0
- package/out/{changelog → chat/join}/__next._index.txt +8 -8
- package/out/chat/join/__next._tree.txt +5 -0
- package/out/chat/join/__next.chat.join.__PAGE__.txt +9 -0
- package/out/chat/join/__next.chat.join.txt +5 -0
- package/out/chat/join/__next.chat.txt +5 -0
- package/out/chat/join/index.html +15 -0
- package/out/chat/join/index.txt +25 -0
- package/out/download/__next._full.txt +37 -33
- package/out/download/__next._head.txt +3 -3
- package/out/download/__next._index.txt +8 -8
- package/out/download/__next._tree.txt +5 -5
- package/out/download/__next.download.__PAGE__.txt +9 -14
- package/out/download/__next.download.txt +3 -3
- package/out/download/index.html +2 -2
- package/out/download/index.txt +37 -33
- package/out/favicon.ico +0 -0
- package/out/gandengyan/__next._full.txt +25 -0
- package/out/{changelog → gandengyan}/__next._head.txt +3 -3
- package/out/{docs → gandengyan}/__next._index.txt +8 -8
- package/out/gandengyan/__next._tree.txt +5 -0
- package/out/gandengyan/__next.gandengyan.__PAGE__.txt +10 -0
- package/out/gandengyan/__next.gandengyan.txt +5 -0
- package/out/gandengyan/index.html +15 -0
- package/out/gandengyan/index.txt +25 -0
- package/out/index.html +2 -2
- package/out/index.txt +16 -16
- package/out/note/__next._full.txt +14 -14
- package/out/note/__next._head.txt +3 -3
- package/out/note/__next._index.txt +8 -8
- package/out/note/__next._tree.txt +3 -3
- package/out/note/__next.note.__PAGE__.txt +4 -4
- package/out/note/__next.note.txt +3 -3
- package/out/note/index.html +2 -2
- package/out/note/index.txt +14 -14
- package/out/ping/__next._full.txt +16 -16
- package/out/ping/__next._head.txt +3 -3
- package/out/ping/__next._index.txt +8 -8
- package/out/ping/__next._tree.txt +5 -5
- package/out/ping/__next.ping.__PAGE__.txt +5 -5
- package/out/ping/__next.ping.txt +4 -4
- package/out/ping/index.html +2 -2
- package/out/ping/index.txt +16 -16
- package/out/web3/__next._full.txt +15 -15
- package/out/web3/__next._head.txt +3 -3
- package/out/web3/__next._index.txt +8 -8
- package/out/web3/__next._tree.txt +4 -4
- package/out/web3/__next.web3.__PAGE__.txt +4 -4
- package/out/web3/__next.web3.txt +4 -4
- package/out/web3/ed25519/__next._full.txt +13 -13
- package/out/web3/ed25519/__next._head.txt +3 -3
- package/out/web3/ed25519/__next._index.txt +8 -8
- package/out/web3/ed25519/__next._tree.txt +4 -4
- package/out/web3/ed25519/__next.web3.ed25519.__PAGE__.txt +2 -2
- package/out/web3/ed25519/__next.web3.ed25519.txt +3 -3
- package/out/web3/ed25519/__next.web3.txt +4 -4
- package/out/web3/ed25519/index.html +1 -1
- package/out/web3/ed25519/index.txt +13 -13
- package/out/web3/index.html +2 -2
- package/out/web3/index.txt +15 -15
- package/out/web3/tools/__next._full.txt +13 -13
- package/out/web3/tools/__next._head.txt +3 -3
- package/out/web3/tools/__next._index.txt +8 -8
- package/out/web3/tools/__next._tree.txt +4 -4
- package/out/web3/tools/__next.web3.tools.__PAGE__.txt +2 -2
- package/out/web3/tools/__next.web3.tools.txt +3 -3
- package/out/web3/tools/__next.web3.txt +4 -4
- package/out/web3/tools/index.html +1 -1
- package/out/web3/tools/index.txt +13 -13
- package/package.json +44 -35
- package/public/favicon.ico +0 -0
- package/server/index.js +142 -1304
- package/server/src/config.js +1 -1
- package/server/src/core/channelAttachment.js +68 -0
- package/server/src/core/cid.js +2 -88
- package/server/src/core/cidTopic.js +29 -0
- package/server/src/core/mostLink.js +88 -0
- package/server/src/games/gandengyan.js +675 -0
- package/server/src/http/access.js +127 -0
- package/server/src/http/app.js +1102 -0
- package/server/src/http/errors.js +35 -0
- package/server/src/http/nodeLogs.js +53 -0
- package/server/src/http/nodeStatus.js +146 -0
- package/server/src/http/staticFiles.js +84 -0
- package/server/src/http/uploads.js +114 -0
- package/server/src/index.js +799 -211
- package/server/src/node/config.js +38 -6
- package/server/src/utils/api.js +305 -14
- package/server/src/utils/auth.js +63 -0
- package/server/src/utils/dateTime.js +30 -0
- package/server/src/utils/downloadMessages.js +89 -0
- package/server/src/utils/errors.js +7 -0
- package/server/src/utils/mostWallet.js +151 -0
- package/server/src/utils/mp.js +2 -26
- package/server/src/utils/noteBackup.js +2 -5
- package/server/src/utils/noteUtils.js +11 -3
- package/server/src/utils/userIdentity.js +0 -1
- package/out/_next/static/chunks/00-u5nq76f0.j.js +0 -1
- package/out/_next/static/chunks/00fm8lijienf1.js +0 -1
- package/out/_next/static/chunks/00o9ht.f2qm00.css +0 -4
- package/out/_next/static/chunks/00zi-erhjrny2.js +0 -2
- package/out/_next/static/chunks/084xf0edl9sfo.js +0 -1
- package/out/_next/static/chunks/09f1gfke9m5wg.css +0 -1
- package/out/_next/static/chunks/09xyi6fpro_d-.css +0 -1
- package/out/_next/static/chunks/0_npg_pcoywti.js +0 -5
- package/out/_next/static/chunks/0_r_mk1~6bosc.js +0 -1
- package/out/_next/static/chunks/0arm0a6adt7cc.css +0 -1
- package/out/_next/static/chunks/0c9j3eq_14vv2.css +0 -1
- package/out/_next/static/chunks/0d4bueddmcnca.js +0 -1
- package/out/_next/static/chunks/0gtwvy1z9ksa7.css +0 -1
- package/out/_next/static/chunks/0ho~log~~-jwp.css +0 -1
- package/out/_next/static/chunks/0j27tcmtt4ly7.js +0 -1
- package/out/_next/static/chunks/0j3v4mq67wtnh.js +0 -1
- package/out/_next/static/chunks/0lkmf5ry.s_7w.js +0 -1
- package/out/_next/static/chunks/0p486m03-zfoi.js +0 -1
- package/out/_next/static/chunks/0r1~k82nji8sf.js +0 -1
- package/out/_next/static/chunks/0v7qp4hv-_._r.js +0 -1
- package/out/_next/static/chunks/0wuwlgcn6gxqt.js +0 -1
- package/out/_next/static/chunks/0xl5_avhu._i8.js +0 -1
- package/out/_next/static/chunks/10kvl8vj_plm-.js +0 -1
- package/out/_next/static/chunks/16m27azcs4k6w.js +0 -1
- package/out/changelog/__next._full.txt +0 -25
- package/out/changelog/__next._tree.txt +0 -5
- package/out/changelog/__next.changelog.__PAGE__.txt +0 -10
- package/out/changelog/__next.changelog.txt +0 -5
- package/out/changelog/index.html +0 -15
- package/out/changelog/index.txt +0 -25
- package/out/docs/__next._full.txt +0 -25
- package/out/docs/__next._head.txt +0 -5
- package/out/docs/__next._tree.txt +0 -5
- package/out/docs/__next.docs.__PAGE__.txt +0 -10
- package/out/docs/__next.docs.txt +0 -5
- package/out/docs/getting-started/__next._full.txt +0 -25
- package/out/docs/getting-started/__next._head.txt +0 -5
- package/out/docs/getting-started/__next._index.txt +0 -9
- package/out/docs/getting-started/__next._tree.txt +0 -5
- package/out/docs/getting-started/__next.docs.getting-started.__PAGE__.txt +0 -10
- package/out/docs/getting-started/__next.docs.getting-started.txt +0 -5
- package/out/docs/getting-started/__next.docs.txt +0 -5
- package/out/docs/getting-started/index.html +0 -15
- package/out/docs/getting-started/index.txt +0 -25
- package/out/docs/index.html +0 -15
- package/out/docs/index.txt +0 -25
- package/out/note/edit/__next._full.txt +0 -24
- package/out/note/edit/__next._head.txt +0 -5
- package/out/note/edit/__next._index.txt +0 -9
- package/out/note/edit/__next._tree.txt +0 -4
- package/out/note/edit/__next.note.edit.__PAGE__.txt +0 -9
- package/out/note/edit/__next.note.edit.txt +0 -5
- package/out/note/edit/__next.note.txt +0 -5
- package/out/note/edit/index.html +0 -15
- package/out/note/edit/index.txt +0 -24
- /package/out/_next/static/{sIuUKxnnGU7K9Tu9UDKE8 → aPEZ4zaaR5W3WpSZ0dFsa}/_buildManifest.js +0 -0
- /package/out/_next/static/{sIuUKxnnGU7K9Tu9UDKE8 → aPEZ4zaaR5W3WpSZ0dFsa}/_clientMiddlewareManifest.js +0 -0
- /package/out/_next/static/{sIuUKxnnGU7K9Tu9UDKE8 → aPEZ4zaaR5W3WpSZ0dFsa}/_ssgManifest.js +0 -0
|
@@ -6,11 +6,11 @@ import { MAX_FILE_SIZE } from '../config.js'
|
|
|
6
6
|
const DEFAULT_CONFIG_DIR_NAME = '.most-box'
|
|
7
7
|
const DEFAULT_DATA_DIR_NAME = 'most-data'
|
|
8
8
|
const DEFAULT_CAPACITY_BYTES = 100 * 1024 * 1024 * 1024
|
|
9
|
+
export const DEFAULT_NODE_PORT = 1976
|
|
10
|
+
export const DEFAULT_NODE_HOST = '127.0.0.1'
|
|
9
11
|
|
|
10
12
|
export function getDefaultConfigDir() {
|
|
11
|
-
return
|
|
12
|
-
? path.resolve(process.env.MOSTBOX_CONFIG_DIR)
|
|
13
|
-
: path.join(os.homedir(), DEFAULT_CONFIG_DIR_NAME)
|
|
13
|
+
return path.join(os.homedir(), DEFAULT_CONFIG_DIR_NAME)
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function getDefaultDataPath() {
|
|
@@ -20,11 +20,21 @@ export function getDefaultDataPath() {
|
|
|
20
20
|
export function getDefaultNodeConfig() {
|
|
21
21
|
return {
|
|
22
22
|
dataPath: '',
|
|
23
|
+
host: DEFAULT_NODE_HOST,
|
|
24
|
+
port: DEFAULT_NODE_PORT,
|
|
23
25
|
capacityBytes: DEFAULT_CAPACITY_BYTES,
|
|
24
26
|
maxFileSizeBytes: MAX_FILE_SIZE,
|
|
27
|
+
remoteInvites: [],
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
|
|
31
|
+
export function normalizeRemoteInvites(value = []) {
|
|
32
|
+
const items = Array.isArray(value) ? value : String(value || '').split(',')
|
|
33
|
+
return Array.from(
|
|
34
|
+
new Set(items.map(item => String(item || '').trim()).filter(Boolean))
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
28
38
|
export function normalizeNodeConfig(raw = {}) {
|
|
29
39
|
const defaults = getDefaultNodeConfig()
|
|
30
40
|
const rawNode = raw.node && typeof raw.node === 'object' ? raw.node : {}
|
|
@@ -37,13 +47,24 @@ export function normalizeNodeConfig(raw = {}) {
|
|
|
37
47
|
rawNode.maxFileSizeBytes ?? raw.maxFileSizeBytes,
|
|
38
48
|
defaults.maxFileSizeBytes
|
|
39
49
|
)
|
|
50
|
+
const remoteInvites = normalizeRemoteInvites(
|
|
51
|
+
rawNode.remoteInvites ?? raw.remoteInvites ?? defaults.remoteInvites
|
|
52
|
+
)
|
|
53
|
+
const host = normalizeHost(rawNode.host ?? raw.host, defaults.host)
|
|
54
|
+
const port = normalizePositiveInteger(
|
|
55
|
+
rawNode.port ?? raw.port,
|
|
56
|
+
defaults.port
|
|
57
|
+
)
|
|
40
58
|
return {
|
|
41
59
|
dataPath:
|
|
42
60
|
typeof raw.dataPath === 'string'
|
|
43
61
|
? raw.dataPath.trim()
|
|
44
62
|
: defaults.dataPath,
|
|
63
|
+
host,
|
|
64
|
+
port,
|
|
45
65
|
capacityBytes,
|
|
46
66
|
maxFileSizeBytes,
|
|
67
|
+
remoteInvites,
|
|
47
68
|
}
|
|
48
69
|
}
|
|
49
70
|
|
|
@@ -82,9 +103,6 @@ export function createNodeConfigStore(configDir = getDefaultConfigDir()) {
|
|
|
82
103
|
}
|
|
83
104
|
|
|
84
105
|
function getDataPath() {
|
|
85
|
-
if (process.env.MOSTBOX_DATA_PATH) {
|
|
86
|
-
return process.env.MOSTBOX_DATA_PATH
|
|
87
|
-
}
|
|
88
106
|
return getNodeConfig().dataPath || getDefaultDataPath()
|
|
89
107
|
}
|
|
90
108
|
|
|
@@ -97,6 +115,8 @@ export function createNodeConfigStore(configDir = getDefaultConfigDir()) {
|
|
|
97
115
|
patch.dataPath === undefined ? current.dataPath : patch.dataPath,
|
|
98
116
|
node: {
|
|
99
117
|
...(raw.node && typeof raw.node === 'object' ? raw.node : {}),
|
|
118
|
+
host: patch.host === undefined ? current.host : patch.host,
|
|
119
|
+
port: patch.port === undefined ? current.port : patch.port,
|
|
100
120
|
capacityBytes:
|
|
101
121
|
patch.capacityBytes === undefined
|
|
102
122
|
? current.capacityBytes
|
|
@@ -105,14 +125,21 @@ export function createNodeConfigStore(configDir = getDefaultConfigDir()) {
|
|
|
105
125
|
patch.maxFileSizeBytes === undefined
|
|
106
126
|
? current.maxFileSizeBytes
|
|
107
127
|
: patch.maxFileSizeBytes,
|
|
128
|
+
remoteInvites:
|
|
129
|
+
patch.remoteInvites === undefined
|
|
130
|
+
? current.remoteInvites
|
|
131
|
+
: patch.remoteInvites,
|
|
108
132
|
},
|
|
109
133
|
})
|
|
110
134
|
|
|
111
135
|
const saved = {
|
|
112
136
|
dataPath: next.dataPath,
|
|
113
137
|
node: {
|
|
138
|
+
host: next.host,
|
|
139
|
+
port: next.port,
|
|
114
140
|
capacityBytes: next.capacityBytes,
|
|
115
141
|
maxFileSizeBytes: next.maxFileSizeBytes,
|
|
142
|
+
remoteInvites: next.remoteInvites,
|
|
116
143
|
updatedAt: new Date().toISOString(),
|
|
117
144
|
},
|
|
118
145
|
}
|
|
@@ -157,3 +184,8 @@ function normalizePositiveInteger(value, fallback) {
|
|
|
157
184
|
}
|
|
158
185
|
return Math.floor(parsed)
|
|
159
186
|
}
|
|
187
|
+
|
|
188
|
+
function normalizeHost(value, fallback) {
|
|
189
|
+
const host = String(value || '').trim()
|
|
190
|
+
return host || fallback
|
|
191
|
+
}
|
package/server/src/utils/api.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import ky from 'ky'
|
|
2
|
+
import { buildAuthHeaders, normalizeAuthPath } from './auth.js'
|
|
2
3
|
|
|
3
4
|
const STORAGE_KEY = 'mostbox_backend_url'
|
|
5
|
+
const INVITE_STORAGE_KEY = 'mostbox_backend_invite'
|
|
4
6
|
const LOCALHOST_BACKEND_URL = 'http://localhost:1976'
|
|
5
7
|
|
|
6
8
|
function isLocalFrontendOrigin() {
|
|
7
9
|
if (typeof window === 'undefined') return false
|
|
8
10
|
|
|
9
|
-
return (
|
|
10
|
-
['localhost', '127.0.0.1'].includes(window.location.hostname) &&
|
|
11
|
-
window.location.port === '3000'
|
|
12
|
-
)
|
|
11
|
+
return ['localhost', '127.0.0.1'].includes(window.location.hostname)
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
function getDefaultBackendUrl() {
|
|
@@ -21,14 +20,136 @@ function getBackendUrl() {
|
|
|
21
20
|
return localStorage.getItem(STORAGE_KEY) || getDefaultBackendUrl()
|
|
22
21
|
}
|
|
23
22
|
|
|
23
|
+
function getConfiguredBackendUrl() {
|
|
24
|
+
if (typeof window === 'undefined') return ''
|
|
25
|
+
return localStorage.getItem(STORAGE_KEY) || ''
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getRemoteBackendUrl() {
|
|
29
|
+
const configured = getConfiguredBackendUrl()
|
|
30
|
+
if (!configured) return ''
|
|
31
|
+
try {
|
|
32
|
+
const { hostname } = new URL(configured)
|
|
33
|
+
return ['localhost', '127.0.0.1', '::1'].includes(hostname)
|
|
34
|
+
? ''
|
|
35
|
+
: configured
|
|
36
|
+
} catch {
|
|
37
|
+
return configured
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getBackendInvite() {
|
|
42
|
+
if (typeof window === 'undefined') return ''
|
|
43
|
+
return localStorage.getItem(INVITE_STORAGE_KEY) || ''
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeBackendUrl(url) {
|
|
47
|
+
return (url || '').trim().replace(/\/+$/, '')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isLocalBackendUrl(url) {
|
|
51
|
+
const value = normalizeBackendUrl(url)
|
|
52
|
+
if (!value) return false
|
|
53
|
+
try {
|
|
54
|
+
const { hostname } = new URL(value)
|
|
55
|
+
const normalized = hostname.toLowerCase()
|
|
56
|
+
return (
|
|
57
|
+
normalized === 'localhost' ||
|
|
58
|
+
normalized === '::1' ||
|
|
59
|
+
normalized === '[::1]' ||
|
|
60
|
+
normalized === '127.0.0.1' ||
|
|
61
|
+
normalized.startsWith('127.')
|
|
62
|
+
)
|
|
63
|
+
} catch {
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function shouldAttachBackendInvite(url = getBackendUrl()) {
|
|
69
|
+
return Boolean(getBackendInvite()) && !isLocalBackendUrl(url)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getStoredIdentity() {
|
|
73
|
+
if (typeof window === 'undefined') return null
|
|
74
|
+
try {
|
|
75
|
+
const raw = localStorage.getItem('mostbox_identity')
|
|
76
|
+
return raw ? JSON.parse(raw) : null
|
|
77
|
+
} catch {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
24
82
|
function normalizePath(path) {
|
|
25
83
|
return path.startsWith('/') ? path : `/${path}`
|
|
26
84
|
}
|
|
27
85
|
|
|
86
|
+
function getBackendAuthPath(url) {
|
|
87
|
+
const requestPath = normalizeAuthPath(url)
|
|
88
|
+
const backendUrl = getBackendUrl()
|
|
89
|
+
if (!backendUrl) return requestPath
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const basePath = new URL(backendUrl).pathname.replace(/\/+$/, '')
|
|
93
|
+
if (!basePath || basePath === '/') return requestPath
|
|
94
|
+
if (requestPath === basePath) return '/'
|
|
95
|
+
if (requestPath.startsWith(`${basePath}/`)) {
|
|
96
|
+
return requestPath.slice(basePath.length)
|
|
97
|
+
}
|
|
98
|
+
} catch {}
|
|
99
|
+
|
|
100
|
+
return requestPath
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function buildWebSocketUrl(base, wsPath = '/ws') {
|
|
104
|
+
const url = new URL(base)
|
|
105
|
+
const wsProtocol = url.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
106
|
+
const basePath = url.pathname.replace(/\/+$/, '')
|
|
107
|
+
return `${wsProtocol}//${url.host}${basePath}${normalizePath(wsPath)}`
|
|
108
|
+
}
|
|
109
|
+
|
|
28
110
|
function createApiInstance() {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
111
|
+
const client = ky.create({
|
|
112
|
+
hooks: {
|
|
113
|
+
beforeRequest: [
|
|
114
|
+
async ({ request }) => {
|
|
115
|
+
const headers = new Headers(request.headers || {})
|
|
116
|
+
const invite = getBackendInvite()
|
|
117
|
+
if (invite && shouldAttachBackendInvite(request.url)) {
|
|
118
|
+
headers.set('x-mostbox-invite', invite)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const identity = getStoredIdentity()
|
|
122
|
+
if (identity?.danger) {
|
|
123
|
+
try {
|
|
124
|
+
const authHeaders = await buildAuthHeaders(
|
|
125
|
+
identity,
|
|
126
|
+
request.method,
|
|
127
|
+
getBackendAuthPath(request.url)
|
|
128
|
+
)
|
|
129
|
+
for (const [key, value] of Object.entries(authHeaders)) {
|
|
130
|
+
headers.set(key, value)
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
// Ignore invalid legacy identity data for public/backend probes.
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return new Request(request, { headers })
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
return new Proxy(client, {
|
|
143
|
+
get(target, prop, receiver) {
|
|
144
|
+
const value = Reflect.get(target, prop, receiver)
|
|
145
|
+
if (!['get', 'post', 'put', 'patch', 'delete', 'head'].includes(prop)) {
|
|
146
|
+
return value
|
|
147
|
+
}
|
|
148
|
+
return (input, options) => {
|
|
149
|
+
const nextInput = typeof input === 'string' ? getApiUrl(input) : input
|
|
150
|
+
return value.call(target, nextInput, options)
|
|
151
|
+
}
|
|
152
|
+
},
|
|
32
153
|
})
|
|
33
154
|
}
|
|
34
155
|
|
|
@@ -71,7 +192,7 @@ export async function getApiErrorMessage(err, fallback = '请求失败') {
|
|
|
71
192
|
err && typeof err === 'object' && 'name' in err ? String(err.name) : ''
|
|
72
193
|
if (errorName === 'TimeoutError') return '请求超时,请稍后重试'
|
|
73
194
|
|
|
74
|
-
if (
|
|
195
|
+
if (err instanceof Error && err.message) {
|
|
75
196
|
return err.message
|
|
76
197
|
}
|
|
77
198
|
|
|
@@ -79,7 +200,7 @@ export async function getApiErrorMessage(err, fallback = '请求失败') {
|
|
|
79
200
|
}
|
|
80
201
|
|
|
81
202
|
export function setBackendUrl(url) {
|
|
82
|
-
const cleaned = (url
|
|
203
|
+
const cleaned = normalizeBackendUrl(url)
|
|
83
204
|
if (cleaned) {
|
|
84
205
|
localStorage.setItem(STORAGE_KEY, cleaned)
|
|
85
206
|
} else {
|
|
@@ -88,37 +209,207 @@ export function setBackendUrl(url) {
|
|
|
88
209
|
api = createApiInstance()
|
|
89
210
|
}
|
|
90
211
|
|
|
212
|
+
export function setBackendInvite(invite) {
|
|
213
|
+
const cleaned = (invite || '').trim()
|
|
214
|
+
if (cleaned) {
|
|
215
|
+
localStorage.setItem(INVITE_STORAGE_KEY, cleaned)
|
|
216
|
+
} else {
|
|
217
|
+
localStorage.removeItem(INVITE_STORAGE_KEY)
|
|
218
|
+
}
|
|
219
|
+
api = createApiInstance()
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function configureBackend({ url, invite }) {
|
|
223
|
+
setBackendUrl(url)
|
|
224
|
+
setBackendInvite(invite)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function clearBackendConnection() {
|
|
228
|
+
setBackendUrl('')
|
|
229
|
+
setBackendInvite('')
|
|
230
|
+
}
|
|
231
|
+
|
|
91
232
|
export function getBackendUrlExport() {
|
|
92
233
|
return getBackendUrl()
|
|
93
234
|
}
|
|
94
235
|
|
|
236
|
+
export function getRemoteBackendUrlExport() {
|
|
237
|
+
return getRemoteBackendUrl()
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function getBackendInviteExport() {
|
|
241
|
+
return getBackendInvite()
|
|
242
|
+
}
|
|
243
|
+
|
|
95
244
|
export function getApiUrl(path) {
|
|
96
245
|
const url = getBackendUrl()
|
|
97
246
|
return `${url}${normalizePath(path)}`
|
|
98
247
|
}
|
|
99
248
|
|
|
249
|
+
export async function getApiRequestHeaders(method = 'GET', path = '/') {
|
|
250
|
+
/** @type {Record<string, string>} */
|
|
251
|
+
const headers = {}
|
|
252
|
+
const invite = getBackendInvite()
|
|
253
|
+
if (invite && shouldAttachBackendInvite()) {
|
|
254
|
+
headers['x-mostbox-invite'] = invite
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
Object.assign(
|
|
258
|
+
headers,
|
|
259
|
+
await buildAuthHeaders(
|
|
260
|
+
getStoredIdentity(),
|
|
261
|
+
method,
|
|
262
|
+
normalizeAuthPath(path)
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
} catch {
|
|
266
|
+
// Callers that require auth will receive the server's 401 response.
|
|
267
|
+
}
|
|
268
|
+
return headers
|
|
269
|
+
}
|
|
270
|
+
|
|
100
271
|
export function getWebSocketUrl(path = '/ws') {
|
|
101
272
|
if (typeof window === 'undefined') return normalizePath(path)
|
|
102
273
|
|
|
103
274
|
const base = getBackendUrl() || window.location.origin
|
|
104
|
-
|
|
105
|
-
|
|
275
|
+
return buildWebSocketUrl(base, path)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export async function getAuthenticatedWebSocketUrl(path = '/ws') {
|
|
279
|
+
if (typeof window === 'undefined') return normalizePath(path)
|
|
280
|
+
|
|
281
|
+
const base = getBackendUrl() || window.location.origin
|
|
282
|
+
const url = new URL(buildWebSocketUrl(base, path))
|
|
283
|
+
|
|
284
|
+
const invite = getBackendInvite()
|
|
285
|
+
if (invite && shouldAttachBackendInvite(url.toString())) {
|
|
286
|
+
url.searchParams.set('invite', invite)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const identity = getStoredIdentity()
|
|
290
|
+
if (identity?.danger) {
|
|
291
|
+
try {
|
|
292
|
+
const auth = await buildAuthHeaders(
|
|
293
|
+
identity,
|
|
294
|
+
'GET',
|
|
295
|
+
normalizeAuthPath(path)
|
|
296
|
+
)
|
|
297
|
+
const [address, timestamp, signature] = String(
|
|
298
|
+
auth.Authorization || ''
|
|
299
|
+
).split(',')
|
|
300
|
+
if (address && signature) {
|
|
301
|
+
url.searchParams.set('address', address)
|
|
302
|
+
url.searchParams.set('timestamp', timestamp)
|
|
303
|
+
url.searchParams.set('signature', signature)
|
|
304
|
+
}
|
|
305
|
+
} catch {
|
|
306
|
+
// Leave WebSocket unauthenticated when local identity data is invalid.
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
106
310
|
return url.toString()
|
|
107
311
|
}
|
|
108
312
|
|
|
109
313
|
export async function checkBackendConnection() {
|
|
110
314
|
const url = getBackendUrl()
|
|
315
|
+
const invite = shouldAttachBackendInvite(url) ? getBackendInvite() : ''
|
|
316
|
+
return checkBackendConnectionTarget({ url, invite })
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function probeHttp(cleanedUrl, invite, identity) {
|
|
111
320
|
try {
|
|
112
|
-
const
|
|
321
|
+
const headers = {}
|
|
322
|
+
if (invite) headers['x-mostbox-invite'] = invite
|
|
323
|
+
try {
|
|
324
|
+
Object.assign(
|
|
325
|
+
headers,
|
|
326
|
+
await buildAuthHeaders(identity, 'GET', '/api/remote/capabilities')
|
|
327
|
+
)
|
|
328
|
+
} catch {
|
|
329
|
+
// Backend detection should still work when old identity data is invalid.
|
|
330
|
+
}
|
|
331
|
+
const res = await fetch(`${cleanedUrl}/api/remote/capabilities`, {
|
|
113
332
|
method: 'GET',
|
|
333
|
+
headers,
|
|
114
334
|
signal: AbortSignal.timeout(3000),
|
|
115
335
|
})
|
|
116
|
-
|
|
336
|
+
if (!res.ok) return { ok: false, reason: 'http' }
|
|
337
|
+
return { ok: true }
|
|
117
338
|
} catch {
|
|
118
|
-
return false
|
|
339
|
+
return { ok: false, reason: 'http' }
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function probeWebSocket(cleanedUrl, invite, identity) {
|
|
344
|
+
if (typeof WebSocket === 'undefined') return { ok: true }
|
|
345
|
+
if (!identity?.danger) return { ok: true }
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
const wsUrl = new URL(buildWebSocketUrl(cleanedUrl))
|
|
349
|
+
|
|
350
|
+
if (invite) {
|
|
351
|
+
wsUrl.searchParams.set('invite', invite)
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (identity?.danger) {
|
|
355
|
+
try {
|
|
356
|
+
const auth = await buildAuthHeaders(
|
|
357
|
+
identity,
|
|
358
|
+
'GET',
|
|
359
|
+
normalizeAuthPath('/ws')
|
|
360
|
+
)
|
|
361
|
+
const [address, timestamp, signature] = String(
|
|
362
|
+
auth.Authorization || ''
|
|
363
|
+
).split(',')
|
|
364
|
+
if (address && signature) {
|
|
365
|
+
wsUrl.searchParams.set('address', address)
|
|
366
|
+
wsUrl.searchParams.set('timestamp', timestamp)
|
|
367
|
+
wsUrl.searchParams.set('signature', signature)
|
|
368
|
+
}
|
|
369
|
+
} catch {
|
|
370
|
+
// Leave WebSocket unauthenticated when local identity data is invalid.
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return await new Promise(resolve => {
|
|
375
|
+
const ws = new WebSocket(wsUrl.toString())
|
|
376
|
+
const timeout = setTimeout(() => {
|
|
377
|
+
ws.close()
|
|
378
|
+
resolve({ ok: false, reason: 'ws' })
|
|
379
|
+
}, 4000)
|
|
380
|
+
|
|
381
|
+
ws.onopen = () => {
|
|
382
|
+
clearTimeout(timeout)
|
|
383
|
+
ws.close()
|
|
384
|
+
resolve({ ok: true })
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
ws.onerror = () => {
|
|
388
|
+
clearTimeout(timeout)
|
|
389
|
+
resolve({ ok: false, reason: 'ws' })
|
|
390
|
+
}
|
|
391
|
+
})
|
|
392
|
+
} catch {
|
|
393
|
+
return { ok: false, reason: 'ws' }
|
|
119
394
|
}
|
|
120
395
|
}
|
|
121
396
|
|
|
397
|
+
export async function checkBackendConnectionTarget({ url, invite = '' }) {
|
|
398
|
+
const cleanedUrl = normalizeBackendUrl(url)
|
|
399
|
+
if (!cleanedUrl) return { ok: false, reason: 'http' }
|
|
400
|
+
|
|
401
|
+
const identity = getStoredIdentity()
|
|
402
|
+
|
|
403
|
+
const [httpResult, wsResult] = await Promise.all([
|
|
404
|
+
probeHttp(cleanedUrl, invite, identity),
|
|
405
|
+
probeWebSocket(cleanedUrl, invite, identity),
|
|
406
|
+
])
|
|
407
|
+
|
|
408
|
+
if (!httpResult.ok) return httpResult
|
|
409
|
+
if (!wsResult.ok) return wsResult
|
|
410
|
+
return { ok: true }
|
|
411
|
+
}
|
|
412
|
+
|
|
122
413
|
export async function detectSameOriginBackend() {
|
|
123
414
|
if (isLocalFrontendOrigin()) return false
|
|
124
415
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { verifyMessage } from 'ethers'
|
|
2
|
+
import { mostSignMessage } from './mostWallet.js'
|
|
3
|
+
|
|
4
|
+
export const AUTH_MAX_AGE_MS = 5 * 60 * 1000
|
|
5
|
+
|
|
6
|
+
export function normalizeAddress(address) {
|
|
7
|
+
const value = String(address || '').trim()
|
|
8
|
+
return /^0x[a-fA-F0-9]{40}$/.test(value) ? value.toLowerCase() : ''
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function buildAuthMessage(timestamp, method, path) {
|
|
12
|
+
return `${timestamp}:${String(method || 'GET').toUpperCase()}:${normalizeAuthPath(path)}`
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function normalizeAuthPath(path) {
|
|
16
|
+
try {
|
|
17
|
+
return new URL(path, 'http://most.box').pathname
|
|
18
|
+
} catch {
|
|
19
|
+
return String(path || '').split('?')[0] || '/'
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function buildAuthHeaders(identity, method, path) {
|
|
24
|
+
if (!identity?.danger) return {}
|
|
25
|
+
const timestamp = Date.now().toString()
|
|
26
|
+
const message = buildAuthMessage(timestamp, method, path)
|
|
27
|
+
const { address, signature } = await mostSignMessage(identity.danger, message)
|
|
28
|
+
return {
|
|
29
|
+
Authorization: `${address},${timestamp},${signature}`,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function verifyAuthHeader(header, method, path, options = {}) {
|
|
34
|
+
const [addressRaw, timestampRaw, signature] = String(header || '').split(',')
|
|
35
|
+
const address = normalizeAddress(addressRaw)
|
|
36
|
+
const timestamp = Number(timestampRaw)
|
|
37
|
+
const now = options.now || Date.now()
|
|
38
|
+
|
|
39
|
+
if (!address || !Number.isFinite(timestamp) || !signature) {
|
|
40
|
+
return { ok: false, error: 'Missing or invalid authorization' }
|
|
41
|
+
}
|
|
42
|
+
if (Math.abs(now - timestamp) > (options.maxAgeMs || AUTH_MAX_AGE_MS)) {
|
|
43
|
+
return { ok: false, error: 'Authorization expired' }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const message = buildAuthMessage(timestampRaw, method, path)
|
|
48
|
+
const recovered = normalizeAddress(verifyMessage(message, signature))
|
|
49
|
+
if (recovered !== address) {
|
|
50
|
+
return { ok: false, error: 'Authorization address mismatch' }
|
|
51
|
+
}
|
|
52
|
+
return { ok: true, address }
|
|
53
|
+
} catch {
|
|
54
|
+
return { ok: false, error: 'Invalid authorization signature' }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function parseInviteList(value = '') {
|
|
59
|
+
return String(value || '')
|
|
60
|
+
.split(',')
|
|
61
|
+
.map(item => item.trim())
|
|
62
|
+
.filter(Boolean)
|
|
63
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import dayjs from 'dayjs'
|
|
2
|
+
|
|
3
|
+
export function formatDate(time) {
|
|
4
|
+
if (!time) return ''
|
|
5
|
+
const date = dayjs(Number(time))
|
|
6
|
+
const hour = date.hour()
|
|
7
|
+
let timeOfDay
|
|
8
|
+
|
|
9
|
+
if (hour >= 0 && hour < 3) {
|
|
10
|
+
timeOfDay = '凌晨'
|
|
11
|
+
} else if (hour >= 3 && hour < 6) {
|
|
12
|
+
timeOfDay = '拂晓'
|
|
13
|
+
} else if (hour >= 6 && hour < 9) {
|
|
14
|
+
timeOfDay = '早晨'
|
|
15
|
+
} else if (hour >= 9 && hour < 12) {
|
|
16
|
+
timeOfDay = '上午'
|
|
17
|
+
} else if (hour >= 12 && hour < 15) {
|
|
18
|
+
timeOfDay = '下午'
|
|
19
|
+
} else if (hour >= 15 && hour < 18) {
|
|
20
|
+
timeOfDay = '傍晚'
|
|
21
|
+
} else if (hour >= 18 && hour < 21) {
|
|
22
|
+
timeOfDay = '晚上'
|
|
23
|
+
} else {
|
|
24
|
+
timeOfDay = '深夜'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return date.format(`YYYY年M月D日 ${timeOfDay}h:m`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const formatTime = formatDate
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { parseMostLink } from '../core/mostLink.js'
|
|
2
|
+
|
|
3
|
+
const DOWNLOAD_CHECK_MESSAGES = {
|
|
4
|
+
timeout:
|
|
5
|
+
'检测等待超时,暂时没有等到在线种子响应。请确认分享者或其他下载者仍在线做种,稍后再检测。',
|
|
6
|
+
offline: '无法连接本地节点,请确认 MostBox 后端正在运行后再检测。',
|
|
7
|
+
missingApi: '当前后端还没有检测接口,请重启 MostBox 后端后再试。',
|
|
8
|
+
validation:
|
|
9
|
+
'链接格式不正确,请粘贴完整的 most://<cid>?filename=... 分享链接。',
|
|
10
|
+
nameConflict: '下载目录已有同名文件,请先重命名或移走后再检测。',
|
|
11
|
+
noPeer:
|
|
12
|
+
'暂时没有发现在线种子。请确认分享者或其他下载者仍在线做种,稍后再检测。',
|
|
13
|
+
permission: '下载目录不可写,请检查目录权限后再检测。',
|
|
14
|
+
starting: '本地节点还没有启动完成,请稍等几秒后重新检测。',
|
|
15
|
+
server: '本地节点检测时出错,请稍后重试或查看节点日志。',
|
|
16
|
+
fallback: '检测未通过,请确认链接完整、发布者在线且本机网络正常。',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const LINK_VALIDATION_MESSAGES = {
|
|
20
|
+
'Link must be a valid most:// URL':
|
|
21
|
+
'链接无法解析,请粘贴完整的 most://<cid>?filename=... 分享链接。',
|
|
22
|
+
'Link must use most:// protocol': '链接协议不正确,应以 most:// 开头。',
|
|
23
|
+
'Link path is not supported':
|
|
24
|
+
'链接里不应包含路径,请使用 most://<cid>?filename=... 格式。',
|
|
25
|
+
'Invalid CID format': 'CID 无效,请确认 most:// 后面的内容没有缺失或被截断。',
|
|
26
|
+
'Invalid CID format: CID v1 required':
|
|
27
|
+
'CID 格式不符合 MostBox 要求,请确认分享链接完整。',
|
|
28
|
+
'CID digest must be 32 bytes':
|
|
29
|
+
'CID 格式不符合 MostBox 要求,请确认分享链接完整。',
|
|
30
|
+
'filename is required':
|
|
31
|
+
'链接缺少 filename 参数,请复制完整分享链接后再检测。',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getDownloadCheckErrorMessageFromPayload(
|
|
35
|
+
data = {},
|
|
36
|
+
errorName = ''
|
|
37
|
+
) {
|
|
38
|
+
if (errorName === 'TimeoutError') return DOWNLOAD_CHECK_MESSAGES.timeout
|
|
39
|
+
if (!data.status) return DOWNLOAD_CHECK_MESSAGES.offline
|
|
40
|
+
if (data.status === 404) return DOWNLOAD_CHECK_MESSAGES.missingApi
|
|
41
|
+
|
|
42
|
+
switch (data.code) {
|
|
43
|
+
case 'VALIDATION_ERROR':
|
|
44
|
+
return DOWNLOAD_CHECK_MESSAGES.validation
|
|
45
|
+
case 'CONFLICT':
|
|
46
|
+
return data.error
|
|
47
|
+
? `${data.error},请先处理同名文件后再下载。`
|
|
48
|
+
: DOWNLOAD_CHECK_MESSAGES.nameConflict
|
|
49
|
+
case 'PEER_NOT_FOUND':
|
|
50
|
+
return DOWNLOAD_CHECK_MESSAGES.noPeer
|
|
51
|
+
case 'PERMISSION_ERROR':
|
|
52
|
+
return data.error
|
|
53
|
+
? `下载目录不可写:${data.error}`
|
|
54
|
+
: DOWNLOAD_CHECK_MESSAGES.permission
|
|
55
|
+
case 'ENGINE_NOT_INITIALIZED':
|
|
56
|
+
return DOWNLOAD_CHECK_MESSAGES.starting
|
|
57
|
+
default:
|
|
58
|
+
break
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (data.status === 503) return DOWNLOAD_CHECK_MESSAGES.noPeer
|
|
62
|
+
if (data.status >= 500) return DOWNLOAD_CHECK_MESSAGES.server
|
|
63
|
+
return data.error
|
|
64
|
+
? `检测未通过:${data.error}`
|
|
65
|
+
: DOWNLOAD_CHECK_MESSAGES.fallback
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getDownloadLinkValidationMessage(link = '') {
|
|
69
|
+
const value = String(link || '').trim()
|
|
70
|
+
if (!value) return '请先粘贴 most:// 分享链接。'
|
|
71
|
+
|
|
72
|
+
const result = parseMostLink(value)
|
|
73
|
+
if (!result.error) {
|
|
74
|
+
return result.fileName?.trim()
|
|
75
|
+
? null
|
|
76
|
+
: LINK_VALIDATION_MESSAGES['filename is required']
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (result.error.startsWith('Unsupported query parameter: ')) {
|
|
80
|
+
const unsupportedParam = result.error.slice(
|
|
81
|
+
'Unsupported query parameter: '.length
|
|
82
|
+
)
|
|
83
|
+
return `链接包含暂不支持的参数 ${unsupportedParam},请只保留 filename。`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
LINK_VALIDATION_MESSAGES[result.error] || DOWNLOAD_CHECK_MESSAGES.validation
|
|
88
|
+
)
|
|
89
|
+
}
|
|
@@ -65,6 +65,13 @@ export class ConflictError extends AppError {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
export class StorageCapacityError extends AppError {
|
|
69
|
+
constructor(message = 'Storage capacity exceeded') {
|
|
70
|
+
super(message, 'STORAGE_CAPACITY_ERROR')
|
|
71
|
+
this.name = 'StorageCapacityError'
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
68
75
|
export class EngineNotInitializedError extends AppError {
|
|
69
76
|
constructor(message = 'Engine not initialized. Call start() first.') {
|
|
70
77
|
super(message, 'ENGINE_NOT_INITIALIZED')
|