most-box 0.2.5 → 0.2.7
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 +6 -0
- package/electron/main.js +58 -4
- package/electron/updateChecker.js +10 -27
- package/electron/updateChecker.test.js +67 -0
- package/out/admin/index.html +0 -0
- package/out/app/index.html +0 -0
- package/out/assets/FilePreviewOverlay-CW8lKJCw.js +1 -0
- package/out/assets/LanguageToggle-Cfh33R2h.js +1 -0
- package/out/assets/{MarketingHeader-B3vYocUz.js → MarketingHeader-BKBngzf5.js} +1 -1
- package/out/assets/MarketingLayout-C3xrXmfU.js +1 -0
- package/out/assets/MarketingThemeToggle-Cl7PhFgv.js +1 -0
- package/out/assets/{MilkdownEditor-CRA9oxMp.js → MilkdownEditor-B5SKAyjg.js} +1 -1
- package/out/assets/OpenSidebarButton-Bdx86oG8.js +1 -0
- package/out/assets/SidebarAccount-c8jirEMF.js +1 -0
- package/out/assets/SidebarHomeLink-LzYXrJRr.js +1 -0
- package/out/assets/arrow-right-BC_GWahx.js +1 -0
- package/out/assets/circle-alert-Dkgyw_GE.js +1 -0
- package/out/assets/copy-KIwotKoP.js +1 -0
- package/out/assets/download-DPA-3LpR.js +1 -0
- package/out/assets/{downloadValidation-eInMiVSX.js → downloadValidation-CH8BeY7_.js} +1 -1
- package/out/assets/external-link-BmR2rZ80.js +1 -0
- package/out/assets/file-text-BJQfDW9r.js +1 -0
- package/out/assets/folder-Gb80aQnO.js +1 -0
- package/out/assets/hard-drive-P2dGA7FX.js +1 -0
- package/out/assets/i18n-Cqyi6NZb.js +1 -0
- package/out/assets/index-CSBg7dZN.css +1 -0
- package/out/assets/index-CiHRnvMj.js +31 -0
- package/out/assets/index.lazy-B7u3Ztoe.js +1 -0
- package/out/assets/index.lazy-BdOfbfjo.js +2 -0
- package/out/assets/index.lazy-Bh4jDrUl.js +1 -0
- package/out/assets/index.lazy-Bo6McUf1.js +1 -0
- package/out/assets/index.lazy-BxUje7fk.js +1 -0
- package/out/assets/index.lazy-CIqPn0cy.js +1 -0
- package/out/assets/index.lazy-CfuoMz6P.js +1 -0
- package/out/assets/index.lazy-DAKkwnV2.js +1 -0
- package/out/assets/index.lazy-DRaxm9OS.js +1 -0
- package/out/assets/index.lazy-DkrwcEBu.js +2 -0
- package/out/assets/index.lazy-K6IB-5kb.js +3 -0
- package/out/assets/index.lazy-Uu9DCtrI.js +1 -0
- package/out/assets/index.lazy-mXESMo49.js +1 -0
- package/out/assets/key-round-DvpIlAJY.js +1 -0
- package/out/assets/loader-8eVY8_SD.js +1 -0
- package/out/assets/lock-Cvtl60UJ.js +1 -0
- package/out/assets/message-square-JK7ABH3O.js +1 -0
- package/out/assets/mp-CMvgV1-c.js +1 -0
- package/out/assets/music-YRSH1Jq6.js +1 -0
- package/out/assets/notebook-pen-BjGx0coX.js +1 -0
- package/out/assets/play-Ds19moWN.js +1 -0
- package/out/assets/plus-CvGkRhJV.js +1 -0
- package/out/assets/save-BcI68ZgS.js +1 -0
- package/out/assets/search-BIa4RSPA.js +1 -0
- package/out/assets/send-_ups0_3f.js +1 -0
- package/out/assets/shield-check-Dzw5rdPe.js +1 -0
- package/out/assets/smartphone-DSQ5L_sr.js +1 -0
- package/out/assets/trash-2-BIrBn6Ce.js +1 -0
- package/out/assets/triangle-alert-CgWIEl4u.js +1 -0
- package/out/assets/ui-u9yJHkb-.js +4 -0
- package/out/assets/upload-1jmNeGjH.js +1 -0
- package/out/assets/{useChannelMessages-D_306Ymv.js → useChannelMessages-DNxfCew-.js} +1 -1
- package/out/assets/useGameRoom-Du07VEgb.js +1 -0
- package/out/assets/userProfile-CL1Ev2GK.js +1 -0
- package/out/assets/wallet-BvM_X5nW.js +1 -0
- package/out/assets/wifi-66t1dsgC.js +1 -0
- package/out/chat/index.html +0 -0
- package/out/chat/join/index.html +0 -0
- package/out/download/index.html +2 -2
- package/out/game/gandengyan/index.html +0 -0
- package/out/game/index.html +0 -0
- package/out/game/zhajinhua/index.html +0 -0
- package/out/index.html +2 -2
- package/out/note/index.html +0 -0
- package/out/ping/index.html +2 -2
- package/out/profile/index.html +0 -0
- package/out/web3/index.html +0 -0
- package/package.json +5 -1
- package/server/src/core/channelIdentity.js +3 -5
- package/server/src/core/gameRoom.js +18 -43
- package/server/src/core/ownerMetadata.js +3 -4
- package/server/src/core/releaseManifest.js +108 -0
- package/server/src/core/shared.js +50 -0
- package/server/src/core/zhajinhua.js +12 -33
- package/server/src/games/gandengyan.js +7 -14
- package/server/src/http/nodeStatus.js +0 -6
- package/server/src/http/routes/channelRoutes.js +1 -13
- package/server/src/http/routes/nodeRoutes.js +1 -1
- package/server/src/index.js +44 -14
- package/server/src/utils/api.js +47 -0
- package/server/src/utils/auth.js +2 -5
- package/server/src/utils/dateTime.js +1 -3
- package/out/assets/AppShell-oF8wPXde.js +0 -1
- package/out/assets/FilePreviewOverlay-DTBZt89J.js +0 -1
- package/out/assets/LanguageToggle-HAJ3D6Ne.js +0 -1
- package/out/assets/MarketingLayout-CapVVXTP.js +0 -1
- package/out/assets/MarketingThemeToggle-B47H68ro.js +0 -1
- package/out/assets/OpenSidebarButton-D3JRO-Hh.js +0 -1
- package/out/assets/SidebarAccount-8_hQAYXx.js +0 -1
- package/out/assets/arrow-right-mNO53HQi.js +0 -1
- package/out/assets/circle-alert-CofRTRHr.js +0 -1
- package/out/assets/cloud-DAGFCFIp.js +0 -1
- package/out/assets/copy-5aEv78Eq.js +0 -1
- package/out/assets/download-Bhqn6hL1.js +0 -1
- package/out/assets/external-link-CNR21yFA.js +0 -1
- package/out/assets/file-text-CFfvvCnM.js +0 -1
- package/out/assets/folder-BGJCjyba.js +0 -1
- package/out/assets/hard-drive-4vHpA5kz.js +0 -1
- package/out/assets/index--p9MKNEl.js +0 -31
- package/out/assets/index-DEu4udaa.css +0 -1
- package/out/assets/index.lazy-2K3f5PZe.js +0 -1
- package/out/assets/index.lazy-B-2etVwK.js +0 -1
- package/out/assets/index.lazy-B4WTQF5E.js +0 -1
- package/out/assets/index.lazy-BFfsQrRe.js +0 -1
- package/out/assets/index.lazy-C5udBnrO.js +0 -1
- package/out/assets/index.lazy-C84HrZpm.js +0 -1
- package/out/assets/index.lazy-CBC2N5p_.js +0 -3
- package/out/assets/index.lazy-CzANViGX.js +0 -1
- package/out/assets/index.lazy-D3cw7yyA.js +0 -2
- package/out/assets/index.lazy-DVVZzbCN.js +0 -1
- package/out/assets/index.lazy-DxZgywoV.js +0 -1
- package/out/assets/index.lazy-RBLN4SE9.js +0 -1
- package/out/assets/index.lazy-s4E2z9AY.js +0 -2
- package/out/assets/key-round-BpSdpOHi.js +0 -1
- package/out/assets/loader-ByMmV2_4.js +0 -1
- package/out/assets/lock-C0pdaery.js +0 -1
- package/out/assets/message-square-Eu86zZup.js +0 -1
- package/out/assets/mp-CJWipckT.js +0 -1
- package/out/assets/music-Btrhub6z.js +0 -1
- package/out/assets/notebook-pen-CfdFgaDE.js +0 -1
- package/out/assets/play-D5r9Yvbq.js +0 -1
- package/out/assets/plus-Mlh7r_bm.js +0 -1
- package/out/assets/refresh-cw--JB8k6mH.js +0 -1
- package/out/assets/save-DN4eprSt.js +0 -1
- package/out/assets/search-CbzMyENy.js +0 -1
- package/out/assets/send-D8p6f8OD.js +0 -1
- package/out/assets/shield-check-c0labdRH.js +0 -1
- package/out/assets/trash-2-zdhWec_U.js +0 -1
- package/out/assets/triangle-alert-CO_5uXy2.js +0 -1
- package/out/assets/upload-BD23c5Pz.js +0 -1
- package/out/assets/useGameRoom-CURx56m6.js +0 -1
- package/out/assets/userProfile-oeBvU6ch.js +0 -1
- package/out/assets/userStore-q0BBZsIV.js +0 -4
- package/out/assets/wallet-DmZMdK9A.js +0 -1
- package/out/assets/wifi-CVqaPDcv.js +0 -1
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createEventId,
|
|
3
|
+
normalizeAddress,
|
|
4
|
+
normalizeAvatar,
|
|
5
|
+
normalizeDisplayName,
|
|
6
|
+
normalizeRoomCode,
|
|
7
|
+
randomInt,
|
|
8
|
+
shortAddress,
|
|
9
|
+
} from './shared.js'
|
|
10
|
+
|
|
11
|
+
export { normalizeAddress } from './shared.js'
|
|
12
|
+
|
|
1
13
|
export const GAME_CHANNEL_TYPE = 'game'
|
|
2
14
|
export const GAME_EVENT_KIND = 'mostbox.game.event'
|
|
3
15
|
export const GAME_EVENT_VERSION = 1
|
|
@@ -12,11 +24,7 @@ export function normalizeGameId(input) {
|
|
|
12
24
|
}
|
|
13
25
|
|
|
14
26
|
export function normalizeGameRoomCode(input) {
|
|
15
|
-
return
|
|
16
|
-
.trim()
|
|
17
|
-
.toUpperCase()
|
|
18
|
-
.replace(/[^A-Z0-9]/g, '')
|
|
19
|
-
.slice(0, 8)
|
|
27
|
+
return normalizeRoomCode(input)
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
export function isValidGameRoomCode(input) {
|
|
@@ -166,10 +174,11 @@ export function createPlayerFromPayload(input, messageOrAuthor) {
|
|
|
166
174
|
const author = message.author
|
|
167
175
|
const address = normalizeAddress(input.address || author)
|
|
168
176
|
if (!address || normalizeAddress(author) !== address) return null
|
|
169
|
-
const name =
|
|
170
|
-
|
|
177
|
+
const name = normalizeDisplayName(
|
|
178
|
+
message.authorName || input.name,
|
|
171
179
|
shortAddress(address)
|
|
172
|
-
|
|
180
|
+
)
|
|
181
|
+
const avatar = normalizeAvatar(message.avatar || input.avatar)
|
|
173
182
|
const publicKey = String(input.publicKey || '').trim()
|
|
174
183
|
const player = {
|
|
175
184
|
address,
|
|
@@ -177,7 +186,7 @@ export function createPlayerFromPayload(input, messageOrAuthor) {
|
|
|
177
186
|
publicKey,
|
|
178
187
|
joinedAt: Number(input.joinedAt || Date.now()),
|
|
179
188
|
}
|
|
180
|
-
if (avatar) player.avatar = avatar
|
|
189
|
+
if (avatar) player.avatar = avatar
|
|
181
190
|
return player
|
|
182
191
|
}
|
|
183
192
|
|
|
@@ -193,11 +202,6 @@ export function normalizeGamePlayer(input) {
|
|
|
193
202
|
}
|
|
194
203
|
}
|
|
195
204
|
|
|
196
|
-
export function normalizeAddress(value) {
|
|
197
|
-
const address = String(value || '').trim()
|
|
198
|
-
return /^0x[a-fA-F0-9]{40}$/.test(address) ? address.toLowerCase() : ''
|
|
199
|
-
}
|
|
200
|
-
|
|
201
205
|
function upsertPlayer(players, playerMap, player) {
|
|
202
206
|
const existing = playerMap.get(player.address)
|
|
203
207
|
if (existing) {
|
|
@@ -213,32 +217,3 @@ function sortMessages(messages) {
|
|
|
213
217
|
(a, b) => Number(a.timestamp || 0) - Number(b.timestamp || 0)
|
|
214
218
|
)
|
|
215
219
|
}
|
|
216
|
-
|
|
217
|
-
function createEventId(prefix) {
|
|
218
|
-
return `${prefix}_${Date.now()}_${randomHex(4)}`
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function randomInt(max) {
|
|
222
|
-
if (globalThis.crypto?.getRandomValues) {
|
|
223
|
-
const value = new Uint32Array(1)
|
|
224
|
-
globalThis.crypto.getRandomValues(value)
|
|
225
|
-
return value[0] % max
|
|
226
|
-
}
|
|
227
|
-
return Math.floor(Math.random() * max)
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function randomHex(byteLength) {
|
|
231
|
-
const bytes = new Uint8Array(byteLength)
|
|
232
|
-
if (globalThis.crypto?.getRandomValues) {
|
|
233
|
-
globalThis.crypto.getRandomValues(bytes)
|
|
234
|
-
} else {
|
|
235
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
236
|
-
bytes[i] = Math.floor(Math.random() * 256)
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return [...bytes].map(byte => byte.toString(16).padStart(2, '0')).join('')
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function shortAddress(address) {
|
|
243
|
-
return address ? `${address.slice(0, 6)}...${address.slice(-4)}` : ''
|
|
244
|
-
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
+
import { normalizeAddress } from './shared.js'
|
|
2
|
+
|
|
1
3
|
export const DEFAULT_OWNER_BUCKET = '__local__'
|
|
2
4
|
|
|
3
|
-
export
|
|
4
|
-
const value = String(address || '').trim()
|
|
5
|
-
return /^0x[a-fA-F0-9]{40}$/.test(value) ? value.toLowerCase() : ''
|
|
6
|
-
}
|
|
5
|
+
export const normalizeOwnerAddress = normalizeAddress
|
|
7
6
|
|
|
8
7
|
export function getOwnerBucketKey(address) {
|
|
9
8
|
return normalizeOwnerAddress(address) || DEFAULT_OWNER_BUCKET
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export const RELEASE_PLATFORMS = ['windows', 'macos', 'linux']
|
|
2
|
+
export const RELEASE_ARCHES = ['x64', 'arm64']
|
|
3
|
+
export const RELEASE_ASSET_KINDS = ['installer', 'updater']
|
|
4
|
+
|
|
5
|
+
export const RELEASE_TARGETS = Object.freeze([
|
|
6
|
+
{ platform: 'windows', arch: 'x64' },
|
|
7
|
+
{ platform: 'windows', arch: 'arm64' },
|
|
8
|
+
{ platform: 'macos', arch: 'x64' },
|
|
9
|
+
{ platform: 'macos', arch: 'arm64' },
|
|
10
|
+
{ platform: 'linux', arch: 'x64' },
|
|
11
|
+
{ platform: 'linux', arch: 'arm64' },
|
|
12
|
+
])
|
|
13
|
+
|
|
14
|
+
const RELEASE_PLATFORM_SET = new Set(RELEASE_PLATFORMS)
|
|
15
|
+
const RELEASE_ARCH_SET = new Set(RELEASE_ARCHES)
|
|
16
|
+
const RELEASE_ASSET_KIND_SET = new Set(RELEASE_ASSET_KINDS)
|
|
17
|
+
|
|
18
|
+
function isRecord(value) {
|
|
19
|
+
return typeof value === 'object' && value !== null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isNonEmptyString(value) {
|
|
23
|
+
return typeof value === 'string' && value.trim().length > 0
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isPositiveFiniteNumber(value) {
|
|
27
|
+
return Number.isFinite(value) && value > 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isOptionalNonEmptyString(value) {
|
|
31
|
+
return typeof value === 'undefined' || isNonEmptyString(value)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isReleaseAsset(value) {
|
|
35
|
+
if (!isRecord(value)) return false
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
RELEASE_PLATFORM_SET.has(value.platform) &&
|
|
39
|
+
RELEASE_ARCH_SET.has(value.arch) &&
|
|
40
|
+
RELEASE_ASSET_KIND_SET.has(value.kind) &&
|
|
41
|
+
isNonEmptyString(value.filename) &&
|
|
42
|
+
isPositiveFiniteNumber(value.size) &&
|
|
43
|
+
isNonEmptyString(value.cid) &&
|
|
44
|
+
isNonEmptyString(value.githubUrl) &&
|
|
45
|
+
isOptionalNonEmptyString(value.r2Url)
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function isReleaseManifest(value) {
|
|
50
|
+
if (!isRecord(value)) return false
|
|
51
|
+
if (!isNonEmptyString(value.version)) return false
|
|
52
|
+
if (!isNonEmptyString(value.publishedAt)) return false
|
|
53
|
+
if (!Array.isArray(value.assets)) return false
|
|
54
|
+
|
|
55
|
+
return value.assets.every(isReleaseAsset)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getReleaseAssetKey(asset) {
|
|
59
|
+
return `${asset.platform}:${asset.arch}`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getInstallerReleaseAssets(manifest) {
|
|
63
|
+
if (!isReleaseManifest(manifest)) return []
|
|
64
|
+
return manifest.assets.filter(asset => asset.kind === 'installer')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function hasR2ReleaseAssets(assets) {
|
|
68
|
+
if (!Array.isArray(assets)) return false
|
|
69
|
+
return assets.some(asset => isRecord(asset) && isNonEmptyString(asset.r2Url))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function resolveReleaseAssetDownload(asset, preferredSource = 'r2') {
|
|
73
|
+
if (
|
|
74
|
+
preferredSource === 'r2' &&
|
|
75
|
+
isRecord(asset) &&
|
|
76
|
+
isNonEmptyString(asset.r2Url)
|
|
77
|
+
) {
|
|
78
|
+
return {
|
|
79
|
+
source: 'r2',
|
|
80
|
+
url: asset.r2Url,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
source: 'github',
|
|
86
|
+
url:
|
|
87
|
+
isRecord(asset) && isNonEmptyString(asset.githubUrl)
|
|
88
|
+
? asset.githubUrl
|
|
89
|
+
: '',
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function findCompatibleUpdateAsset(manifest, platform, arch) {
|
|
94
|
+
if (!isReleaseManifest(manifest)) return null
|
|
95
|
+
|
|
96
|
+
const compatibleAssets = manifest.assets.filter(
|
|
97
|
+
asset =>
|
|
98
|
+
asset.platform === platform &&
|
|
99
|
+
asset.arch === arch &&
|
|
100
|
+
(asset.kind === 'updater' || asset.kind === 'installer')
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
compatibleAssets.find(asset => asset.kind === 'updater') ||
|
|
105
|
+
compatibleAssets.find(asset => asset.kind === 'installer') ||
|
|
106
|
+
null
|
|
107
|
+
)
|
|
108
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export function normalizeAddress(value) {
|
|
2
|
+
const address = String(value || '').trim()
|
|
3
|
+
return /^0x[a-fA-F0-9]{40}$/.test(address) ? address.toLowerCase() : ''
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function shortAddress(address) {
|
|
7
|
+
return address ? `${address.slice(0, 6)}...${address.slice(-4)}` : ''
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function normalizeAvatar(value) {
|
|
11
|
+
return String(value || '').trim().slice(0, 4096)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function normalizeDisplayName(value, fallback = '', maxLength = 50) {
|
|
15
|
+
const displayName = String(value || '').trim()
|
|
16
|
+
return (displayName || String(fallback || '')).slice(0, maxLength)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function normalizeRoomCode(value) {
|
|
20
|
+
return String(value || '')
|
|
21
|
+
.trim()
|
|
22
|
+
.toUpperCase()
|
|
23
|
+
.replace(/[^A-Z0-9]/g, '')
|
|
24
|
+
.slice(0, 8)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createEventId(prefix) {
|
|
28
|
+
return `${prefix}_${Date.now()}_${randomHex(4)}`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function randomInt(max) {
|
|
32
|
+
if (globalThis.crypto?.getRandomValues) {
|
|
33
|
+
const value = new Uint32Array(1)
|
|
34
|
+
globalThis.crypto.getRandomValues(value)
|
|
35
|
+
return value[0] % max
|
|
36
|
+
}
|
|
37
|
+
return Math.floor(Math.random() * max)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function randomHex(byteLength) {
|
|
41
|
+
const bytes = new Uint8Array(byteLength)
|
|
42
|
+
if (globalThis.crypto?.getRandomValues) {
|
|
43
|
+
globalThis.crypto.getRandomValues(bytes)
|
|
44
|
+
} else {
|
|
45
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
46
|
+
bytes[i] = Math.floor(Math.random() * 256)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return [...bytes].map(byte => byte.toString(16).padStart(2, '0')).join('')
|
|
50
|
+
}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createEventId as createSharedEventId,
|
|
3
|
+
normalizeAddress,
|
|
4
|
+
normalizeAvatar,
|
|
5
|
+
normalizeRoomCode as normalizeSharedRoomCode,
|
|
6
|
+
shortAddress,
|
|
7
|
+
} from './shared.js'
|
|
8
|
+
|
|
1
9
|
export const ZHJ_INITIAL_CHIPS = 1000
|
|
2
10
|
export const ZHJ_ANTE = 10
|
|
3
11
|
export const ZHJ_MIN_PLAYERS = 2
|
|
@@ -28,15 +36,11 @@ const CATEGORY_LABEL = {
|
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
export function normalizeRoomCode(input) {
|
|
31
|
-
return
|
|
32
|
-
.trim()
|
|
33
|
-
.toUpperCase()
|
|
34
|
-
.replace(/[^A-Z0-9]/g, '')
|
|
35
|
-
.slice(0, 8)
|
|
39
|
+
return normalizeSharedRoomCode(input)
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
export function createEventId(prefix = 'zhajinhua') {
|
|
39
|
-
return
|
|
43
|
+
return createSharedEventId(prefix)
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
export function createRoundId() {
|
|
@@ -196,7 +200,7 @@ export function startRound({ roomCode, players, hostAddress, previousSeq = 0, pr
|
|
|
196
200
|
return {
|
|
197
201
|
address: player.address,
|
|
198
202
|
name: player.name,
|
|
199
|
-
avatar:
|
|
203
|
+
avatar: normalizeAvatar(player.avatar),
|
|
200
204
|
publicKey: player.publicKey,
|
|
201
205
|
chips: Number(player.chips) - ZHJ_ANTE,
|
|
202
206
|
status: 'active',
|
|
@@ -598,7 +602,7 @@ function normalizeRoundPlayer(input) {
|
|
|
598
602
|
return {
|
|
599
603
|
address,
|
|
600
604
|
name: String(input.name || shortAddress(address)).slice(0, 50),
|
|
601
|
-
avatar:
|
|
605
|
+
avatar: normalizeAvatar(input.avatar),
|
|
602
606
|
publicKey: String(input.publicKey || ''),
|
|
603
607
|
chips: Math.max(0, Number(input.chips) || 0),
|
|
604
608
|
status: input.status === 'folded' ? 'folded' : 'active',
|
|
@@ -607,31 +611,6 @@ function normalizeRoundPlayer(input) {
|
|
|
607
611
|
}
|
|
608
612
|
}
|
|
609
613
|
|
|
610
|
-
function normalizeAddress(value) {
|
|
611
|
-
const address = String(value || '').trim()
|
|
612
|
-
return /^0x[a-fA-F0-9]{40}$/.test(address) ? address.toLowerCase() : ''
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
function shortAddress(address) {
|
|
616
|
-
return address ? `${address.slice(0, 6)}...${address.slice(-4)}` : ''
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
function cleanAvatar(value) {
|
|
620
|
-
return String(value || '').trim().slice(0, 4096)
|
|
621
|
-
}
|
|
622
|
-
|
|
623
614
|
function clone(value) {
|
|
624
615
|
return JSON.parse(JSON.stringify(value))
|
|
625
616
|
}
|
|
626
|
-
|
|
627
|
-
function randomHex(byteLength) {
|
|
628
|
-
const bytes = new Uint8Array(byteLength)
|
|
629
|
-
if (globalThis.crypto?.getRandomValues) {
|
|
630
|
-
globalThis.crypto.getRandomValues(bytes)
|
|
631
|
-
} else {
|
|
632
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
633
|
-
bytes[i] = Math.floor(Math.random() * 256)
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
return [...bytes].map(byte => byte.toString(16).padStart(2, '0')).join('')
|
|
637
|
-
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { normalizeAddress, normalizeAvatar } from '../core/shared.js'
|
|
2
|
+
|
|
1
3
|
const SUITS = ['S', 'H', 'C', 'D']
|
|
2
4
|
const RANKS = ['3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A', '2']
|
|
3
5
|
const STRAIGHT_RANKS = RANKS.filter(rank => rank !== '2')
|
|
@@ -30,7 +32,7 @@ export function createGanDengYanRoom({
|
|
|
30
32
|
players: roomPlayers.slice(0, 6).map((player, seat) => ({
|
|
31
33
|
address: player.address,
|
|
32
34
|
name: cleanName(player.name),
|
|
33
|
-
avatar:
|
|
35
|
+
avatar: normalizeAvatar(player.avatar),
|
|
34
36
|
seat,
|
|
35
37
|
hand: [],
|
|
36
38
|
handCount: 0,
|
|
@@ -64,7 +66,7 @@ export function syncGanDengYanLobby(room, players = []) {
|
|
|
64
66
|
.map((player, seat) => ({
|
|
65
67
|
address: player.address,
|
|
66
68
|
name: cleanName(player.name),
|
|
67
|
-
avatar:
|
|
69
|
+
avatar: normalizeAvatar(player.avatar),
|
|
68
70
|
seat,
|
|
69
71
|
hand: [],
|
|
70
72
|
handCount: 0,
|
|
@@ -260,7 +262,7 @@ export function publicGanDengYanRoom(room) {
|
|
|
260
262
|
players: orderedPlayers(room).map(player => ({
|
|
261
263
|
address: player.address,
|
|
262
264
|
name: player.name,
|
|
263
|
-
avatar:
|
|
265
|
+
avatar: normalizeAvatar(player.avatar),
|
|
264
266
|
seat: player.seat,
|
|
265
267
|
handCount: player.hand?.length ?? player.handCount ?? 0,
|
|
266
268
|
score: Number(player.score ?? INITIAL_SCORE),
|
|
@@ -435,7 +437,7 @@ function normalizePlayers(players) {
|
|
|
435
437
|
.map(player => ({
|
|
436
438
|
address: normalizeAddress(player.address),
|
|
437
439
|
name: cleanName(player.name),
|
|
438
|
-
avatar:
|
|
440
|
+
avatar: normalizeAvatar(player.avatar),
|
|
439
441
|
publicKey: String(player.publicKey || ''),
|
|
440
442
|
}))
|
|
441
443
|
.filter(player => {
|
|
@@ -451,7 +453,7 @@ function normalizeRoundPlayer(input) {
|
|
|
451
453
|
return {
|
|
452
454
|
address,
|
|
453
455
|
name: cleanName(input.name),
|
|
454
|
-
avatar:
|
|
456
|
+
avatar: normalizeAvatar(input.avatar),
|
|
455
457
|
seat: Number(input.seat || 0),
|
|
456
458
|
hand: Array.isArray(input.hand) ? input.hand.map(normalizeCard).filter(Boolean) : [],
|
|
457
459
|
handCount: Number(input.handCount || 0),
|
|
@@ -677,15 +679,6 @@ function rollDice(random) {
|
|
|
677
679
|
return Math.floor(random() * 6) + 1
|
|
678
680
|
}
|
|
679
681
|
|
|
680
|
-
function normalizeAddress(value) {
|
|
681
|
-
const address = String(value || '').trim()
|
|
682
|
-
return /^0x[a-fA-F0-9]{40}$/.test(address) ? address.toLowerCase() : ''
|
|
683
|
-
}
|
|
684
|
-
|
|
685
682
|
function cleanName(name) {
|
|
686
683
|
return String(name || '玩家').trim().slice(0, 16) || '玩家'
|
|
687
684
|
}
|
|
688
|
-
|
|
689
|
-
function cleanAvatar(avatar) {
|
|
690
|
-
return String(avatar || '').trim().slice(0, 4096)
|
|
691
|
-
}
|
|
@@ -260,12 +260,6 @@ export function buildOpenApiSpec(appPort) {
|
|
|
260
260
|
responses: { 200: { description: 'Created channel message' } },
|
|
261
261
|
},
|
|
262
262
|
},
|
|
263
|
-
'/api/channels/{name}/members': {
|
|
264
|
-
get: {
|
|
265
|
-
summary: 'List P2P channel members',
|
|
266
|
-
responses: { 200: { description: 'Channel members' } },
|
|
267
|
-
},
|
|
268
|
-
},
|
|
269
263
|
'/api/channels/{name}/peers': {
|
|
270
264
|
get: {
|
|
271
265
|
summary: 'List currently connected channel peers',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { normalizeAddress } from '../../
|
|
1
|
+
import { normalizeAddress } from '../../core/shared.js'
|
|
2
2
|
import { badRequestOrAppError } from '../errors.js'
|
|
3
3
|
|
|
4
4
|
export function registerChannelRoutes(app, { engine }) {
|
|
@@ -72,18 +72,6 @@ export function registerChannelRoutes(app, { engine }) {
|
|
|
72
72
|
}
|
|
73
73
|
})
|
|
74
74
|
|
|
75
|
-
app.get('/api/channels/:name/members', c => {
|
|
76
|
-
try {
|
|
77
|
-
return c.json(
|
|
78
|
-
engine.getChannelMembers(c.req.param('name'), {
|
|
79
|
-
ownerAddress: c.get('userAddress'),
|
|
80
|
-
})
|
|
81
|
-
)
|
|
82
|
-
} catch (err) {
|
|
83
|
-
return badRequestOrAppError(c, err)
|
|
84
|
-
}
|
|
85
|
-
})
|
|
86
|
-
|
|
87
75
|
app.post('/api/channels/:name/messages', async c => {
|
|
88
76
|
const name = c.req.param('name')
|
|
89
77
|
const body = await c.req.json()
|
package/server/src/index.js
CHANGED
|
@@ -93,6 +93,7 @@ import {
|
|
|
93
93
|
} from './config.js'
|
|
94
94
|
|
|
95
95
|
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
|
96
|
+
const CHANNEL_WELCOME_MESSAGE = '我加入了群聊'
|
|
96
97
|
|
|
97
98
|
function createMemoryDuplexPair() {
|
|
98
99
|
let left
|
|
@@ -2462,6 +2463,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2462
2463
|
channel => channel.channelKey === candidate.channelKey
|
|
2463
2464
|
)
|
|
2464
2465
|
if (existing) {
|
|
2466
|
+
const wasMember = this.#channelHasMember(existing, ownerAddress)
|
|
2465
2467
|
const writerKeysChanged = await this.#mergeChannelWriterCoreKeys(
|
|
2466
2468
|
existing,
|
|
2467
2469
|
candidate.writerCoreKeys
|
|
@@ -2472,6 +2474,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2472
2474
|
this.#saveChannelsMetadata()
|
|
2473
2475
|
this.#broadcastChannelHello()
|
|
2474
2476
|
}
|
|
2477
|
+
await this.#appendChannelWelcomeMessage(existing, options, wasMember)
|
|
2475
2478
|
return this.#formatChannelForResponse(existing, ownerAddress)
|
|
2476
2479
|
}
|
|
2477
2480
|
const joined = await this.#joinChannelFromCandidate(
|
|
@@ -2528,6 +2531,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2528
2531
|
const channelKey = buildChannelKey(channelId)
|
|
2529
2532
|
const existing = this.#channels.find(c => c.channelKey === channelKey)
|
|
2530
2533
|
if (existing) {
|
|
2534
|
+
const wasMember = this.#channelHasMember(existing, options.ownerAddress)
|
|
2531
2535
|
const writerKeysChanged = await this.#mergeChannelWriterCoreKeys(
|
|
2532
2536
|
existing,
|
|
2533
2537
|
candidate.writerCoreKeys
|
|
@@ -2538,6 +2542,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2538
2542
|
this.#saveChannelsMetadata()
|
|
2539
2543
|
this.#broadcastChannelHello()
|
|
2540
2544
|
}
|
|
2545
|
+
await this.#appendChannelWelcomeMessage(existing, options, wasMember)
|
|
2541
2546
|
return this.#formatChannelForResponse(existing, options.ownerAddress)
|
|
2542
2547
|
}
|
|
2543
2548
|
|
|
@@ -2730,14 +2735,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2730
2735
|
.map(c => this.#formatChannelForResponse(c, ownerAddress))
|
|
2731
2736
|
}
|
|
2732
2737
|
|
|
2733
|
-
getChannelMembers(channelKeyInput, options = {}) {
|
|
2734
|
-
this.#ensureInitialized()
|
|
2735
|
-
this.#assertChannelMember(channelKeyInput, options.ownerAddress)
|
|
2736
|
-
const channel = this.#resolveChannel(channelKeyInput, options.ownerAddress)
|
|
2737
|
-
|
|
2738
|
-
return this.#getChannelMembers(channel)
|
|
2739
|
-
}
|
|
2740
|
-
|
|
2741
2738
|
/**
|
|
2742
2739
|
* 获取频道消息
|
|
2743
2740
|
* @param {string} channelKeyInput - 内部频道 key,或本地唯一短频道 ID
|
|
@@ -2843,15 +2840,22 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2843
2840
|
this.#saveChannelsMetadata()
|
|
2844
2841
|
}
|
|
2845
2842
|
|
|
2843
|
+
const normalizedAvatar = normalizeChannelAvatar(options.avatar)
|
|
2846
2844
|
const message = {
|
|
2847
2845
|
type: 'message',
|
|
2848
2846
|
author,
|
|
2849
|
-
authorName
|
|
2847
|
+
authorName: normalizeChannelDisplayName(
|
|
2848
|
+
authorName,
|
|
2849
|
+
normalizeOwnerAddress(author)
|
|
2850
|
+
),
|
|
2850
2851
|
content: trimmed,
|
|
2851
2852
|
timestamp: await this.#getNextChannelMessageTimestamp(
|
|
2852
2853
|
channel.channelKey
|
|
2853
2854
|
),
|
|
2854
2855
|
}
|
|
2856
|
+
if (normalizedAvatar) {
|
|
2857
|
+
message.avatar = normalizedAvatar
|
|
2858
|
+
}
|
|
2855
2859
|
if (attachment) {
|
|
2856
2860
|
message.attachment = attachment
|
|
2857
2861
|
}
|
|
@@ -2977,6 +2981,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2977
2981
|
await this.#joinChannelDiscoveryTopics(channelInfo)
|
|
2978
2982
|
this.#cacheChannelCandidate(this.#channelToCandidate(channelInfo, true))
|
|
2979
2983
|
this.#saveChannelsMetadata()
|
|
2984
|
+
await this.#appendChannelWelcomeMessage(channelInfo, options, false)
|
|
2980
2985
|
this.#broadcastChannelHello()
|
|
2981
2986
|
return channelInfo
|
|
2982
2987
|
}
|
|
@@ -2994,10 +2999,12 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2994
2999
|
channel => channel.channelKey === channelKey
|
|
2995
3000
|
)
|
|
2996
3001
|
if (existing) {
|
|
3002
|
+
const wasMember = this.#channelHasMember(existing, options.ownerAddress)
|
|
2997
3003
|
if (this.#upsertChannelMember(existing, options)) {
|
|
2998
3004
|
this.#saveChannelsMetadata()
|
|
2999
3005
|
this.#broadcastChannelHello()
|
|
3000
3006
|
}
|
|
3007
|
+
await this.#appendChannelWelcomeMessage(existing, options, wasMember)
|
|
3001
3008
|
return this.#formatChannelForResponse(existing, options.ownerAddress)
|
|
3002
3009
|
}
|
|
3003
3010
|
|
|
@@ -3328,6 +3335,32 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
3328
3335
|
return true
|
|
3329
3336
|
}
|
|
3330
3337
|
|
|
3338
|
+
async #appendChannelWelcomeMessage(channel, options = {}, wasMember = false) {
|
|
3339
|
+
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
3340
|
+
if (
|
|
3341
|
+
wasMember ||
|
|
3342
|
+
!ownerAddress ||
|
|
3343
|
+
!channel ||
|
|
3344
|
+
TRANSIENT_CHANNEL_TYPES.has(channel.type)
|
|
3345
|
+
) {
|
|
3346
|
+
return false
|
|
3347
|
+
}
|
|
3348
|
+
|
|
3349
|
+
await this.sendMessage(
|
|
3350
|
+
channel.channelKey,
|
|
3351
|
+
CHANNEL_WELCOME_MESSAGE,
|
|
3352
|
+
ownerAddress,
|
|
3353
|
+
normalizeChannelDisplayName(options.displayName, ownerAddress),
|
|
3354
|
+
{
|
|
3355
|
+
ownerAddress,
|
|
3356
|
+
...(Object.prototype.hasOwnProperty.call(options, 'avatar')
|
|
3357
|
+
? { avatar: options.avatar }
|
|
3358
|
+
: {}),
|
|
3359
|
+
}
|
|
3360
|
+
)
|
|
3361
|
+
return true
|
|
3362
|
+
}
|
|
3363
|
+
|
|
3331
3364
|
#getChannelMembers(channel) {
|
|
3332
3365
|
const members = Array.isArray(channel?.members) ? channel.members : []
|
|
3333
3366
|
return members
|
|
@@ -3392,14 +3425,11 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
3392
3425
|
authorAddress
|
|
3393
3426
|
)
|
|
3394
3427
|
const avatar = normalizeChannelAvatar(member.avatar)
|
|
3395
|
-
if (displayName && baseMessage?.authorName
|
|
3428
|
+
if (displayName && !String(baseMessage?.authorName || '').trim()) {
|
|
3396
3429
|
baseMessage = { ...baseMessage, authorName: displayName }
|
|
3397
3430
|
}
|
|
3398
|
-
if (avatar && baseMessage?.avatar
|
|
3431
|
+
if (avatar && !normalizeChannelAvatar(baseMessage?.avatar)) {
|
|
3399
3432
|
baseMessage = { ...baseMessage, avatar }
|
|
3400
|
-
} else if (!avatar && baseMessage?.avatar) {
|
|
3401
|
-
baseMessage = { ...baseMessage }
|
|
3402
|
-
delete baseMessage.avatar
|
|
3403
3433
|
}
|
|
3404
3434
|
}
|
|
3405
3435
|
const attachment = baseMessage?.attachment
|
package/server/src/utils/api.js
CHANGED
|
@@ -164,6 +164,49 @@ function clearActiveRemoteNode() {
|
|
|
164
164
|
)
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
function getLocalNodeHistoryItem(activeBackendUrl = getBackendUrl()) {
|
|
168
|
+
const normalizedActiveUrl = normalizeBackendUrl(activeBackendUrl)
|
|
169
|
+
const fallbackLocalUrl = getDefaultBackendUrl()
|
|
170
|
+
const localUrl = isLocalBackendUrl(normalizedActiveUrl)
|
|
171
|
+
? normalizedActiveUrl
|
|
172
|
+
: fallbackLocalUrl
|
|
173
|
+
|
|
174
|
+
if (!isLocalBackendUrl(localUrl)) return null
|
|
175
|
+
|
|
176
|
+
const url = normalizeBackendUrl(localUrl)
|
|
177
|
+
return {
|
|
178
|
+
url,
|
|
179
|
+
invite: '',
|
|
180
|
+
active: url === normalizedActiveUrl,
|
|
181
|
+
local: true,
|
|
182
|
+
updatedAt: Number.MAX_SAFE_INTEGER,
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function getNodeHistory() {
|
|
187
|
+
if (typeof window === 'undefined') return []
|
|
188
|
+
|
|
189
|
+
const activeBackendUrl = normalizeBackendUrl(getBackendUrl())
|
|
190
|
+
const localNode = getLocalNodeHistoryItem(activeBackendUrl)
|
|
191
|
+
const remoteNodes = getRemoteNodes().map((node, index) => ({
|
|
192
|
+
...node,
|
|
193
|
+
active: normalizeBackendUrl(node.url) === activeBackendUrl,
|
|
194
|
+
local: false,
|
|
195
|
+
order: index + 1,
|
|
196
|
+
}))
|
|
197
|
+
const nodes = localNode
|
|
198
|
+
? [{ ...localNode, order: 0 }, ...remoteNodes]
|
|
199
|
+
: remoteNodes
|
|
200
|
+
|
|
201
|
+
return nodes
|
|
202
|
+
.sort((a, b) => {
|
|
203
|
+
if (a.active !== b.active) return a.active ? -1 : 1
|
|
204
|
+
if (a.local !== b.local) return a.local ? -1 : 1
|
|
205
|
+
return a.order - b.order
|
|
206
|
+
})
|
|
207
|
+
.map(({ order: _order, ...node }) => node)
|
|
208
|
+
}
|
|
209
|
+
|
|
167
210
|
function normalizeBackendUrl(url) {
|
|
168
211
|
return (url || '').trim().replace(/\/+$/, '')
|
|
169
212
|
}
|
|
@@ -390,6 +433,10 @@ export function getRemoteNodesExport() {
|
|
|
390
433
|
return getRemoteNodes()
|
|
391
434
|
}
|
|
392
435
|
|
|
436
|
+
export function getNodeHistoryExport() {
|
|
437
|
+
return getNodeHistory()
|
|
438
|
+
}
|
|
439
|
+
|
|
393
440
|
export function getBackendInviteExport() {
|
|
394
441
|
return getBackendInvite()
|
|
395
442
|
}
|
package/server/src/utils/auth.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { verifyMessage } from 'ethers'
|
|
2
|
+
import { normalizeAddress } from '../core/shared.js'
|
|
2
3
|
import { mostSignMessage } from './mostWallet.js'
|
|
3
4
|
|
|
4
5
|
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
|
-
}
|
|
6
|
+
export { normalizeAddress }
|
|
10
7
|
|
|
11
8
|
export function buildAuthMessage(timestamp, method, path) {
|
|
12
9
|
return `${timestamp}:${String(method || 'GET').toUpperCase()}:${normalizeAuthPath(path)}`
|