most-box 0.2.9 → 0.3.0
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/out/admin/index.html +0 -0
- package/out/app/index.html +0 -0
- package/out/assets/{FilePreviewOverlay-BYZIAMJ-.js → FilePreviewOverlay-CM5IqnZD.js} +1 -1
- package/out/assets/{MarketingHeader-ceWdRCpe.js → MarketingHeader-B45AaM29.js} +1 -1
- package/out/assets/{MarketingLayout-Bun1d2wL.js → MarketingLayout-A00pEOa2.js} +1 -1
- package/out/assets/{MilkdownEditor-CT6SHYKG.js → MilkdownEditor-BTq9bDAf.js} +1 -1
- package/out/assets/{OpenSidebarButton-CLSCB7CG.js → OpenSidebarButton-D8qfUGzU.js} +1 -1
- package/out/assets/{SidebarAccount-CNh0kvtB.js → SidebarAccount-_RBE5K_o.js} +1 -1
- package/out/assets/{SidebarHomeLink-DXv0c9Ob.js → SidebarHomeLink-qMFEGjZt.js} +1 -1
- package/out/assets/{downloadValidation-Bzl7cci9.js → downloadValidation-CSXwoQKW.js} +1 -1
- package/out/assets/index-BRhsWNj1.css +1 -0
- package/out/assets/{index-BI8oNSVC.js → index-C-_ZwxuN.js} +2 -2
- package/out/assets/index-CYVRmppz.css +1 -0
- package/out/assets/index.lazy-0YYV0CR3.js +1 -0
- package/out/assets/index.lazy-BTYGr9Vb.js +1 -0
- package/out/assets/{index.lazy-BOMHingE.js → index.lazy-BVAtQtTa.js} +1 -1
- package/out/assets/{index.lazy-ur0joIFl.js → index.lazy-BbdLA2L-.js} +1 -1
- package/out/assets/{index.lazy-bAp_Hgck.js → index.lazy-CbiLtziY.js} +1 -1
- package/out/assets/{index.lazy-D4zNn3D6.js → index.lazy-CcEdBg3i.js} +2 -2
- package/out/assets/index.lazy-CePHsLXG.js +1 -0
- package/out/assets/{index.lazy-Ch4Y17pJ.js → index.lazy-DG_3_VdF.js} +1 -1
- package/out/assets/{index.lazy-7omTRykl.js → index.lazy-DR0rWEWZ.js} +1 -1
- package/out/assets/{index.lazy-BSY5_gby.js → index.lazy-DSyTSCJD.js} +1 -1
- package/out/assets/{index.lazy-C4ipXKZ7.js → index.lazy-DUhsuPLv.js} +1 -1
- package/out/assets/{index.lazy-_mEd0Q_u.js → index.lazy-DVnhWbzH.js} +1 -1
- package/out/assets/{index.lazy-CPZbEnJZ.js → index.lazy-DZpkg1DJ.js} +1 -1
- package/out/assets/useChannelMessages-DOaWHdAP.js +4 -0
- package/out/assets/{useGameRoom-cs9YpYt7.js → useGameRoom-hHkV65ii.js} +1 -1
- package/out/assets/{userProfile-qcc60tHK.js → userProfile-DwZip0Dt.js} +1 -1
- 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 +1 -1
- package/server/index.js +59 -2
- package/server/src/http/app.js +7 -1
- package/server/src/http/nodeStatus.js +6 -0
- package/server/src/http/routes/channelRoutes.js +12 -0
- package/server/src/index.js +476 -3
- package/out/assets/index-3OD3Chi9.css +0 -1
- package/out/assets/index-BSOvFG3o.css +0 -1
- package/out/assets/index.lazy-CvBIIYbm.js +0 -1
- package/out/assets/index.lazy-Cz-OZwYL.js +0 -1
- package/out/assets/index.lazy-DRY3xoQp.js +0 -1
- package/out/assets/useChannelMessages-BeKF0gwo.js +0 -3
package/server/src/index.js
CHANGED
|
@@ -93,6 +93,8 @@ import {
|
|
|
93
93
|
} from './config.js'
|
|
94
94
|
|
|
95
95
|
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
|
96
|
+
const CHANNEL_PRESENCE_HEARTBEAT_MS = 15 * 1000
|
|
97
|
+
const CHANNEL_PRESENCE_TIMEOUT_MS = 45 * 1000
|
|
96
98
|
const CHANNEL_WELCOME_MESSAGE = '我加入了群聊'
|
|
97
99
|
|
|
98
100
|
function createMemoryDuplexPair() {
|
|
@@ -158,6 +160,9 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
158
160
|
#channelPeers = new Map()
|
|
159
161
|
#channelCandidateCache = new Map()
|
|
160
162
|
#channelStreams = new Set()
|
|
163
|
+
#channelPresenceSessions = new Map()
|
|
164
|
+
#channelPresenceProfiles = new Map()
|
|
165
|
+
#channelPresenceSweepTimer = null
|
|
161
166
|
|
|
162
167
|
#accountMetadata = { profiles: {} }
|
|
163
168
|
|
|
@@ -190,6 +195,90 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
190
195
|
}
|
|
191
196
|
}
|
|
192
197
|
|
|
198
|
+
getChannelPresence(channelKeyInput, options = {}) {
|
|
199
|
+
this.#ensureInitialized()
|
|
200
|
+
this.#assertChannelMember(channelKeyInput, options.ownerAddress)
|
|
201
|
+
const channel = this.#resolveChannel(channelKeyInput, options.ownerAddress)
|
|
202
|
+
this.#pruneStaleChannelPresence()
|
|
203
|
+
return this.#getChannelPresenceList(channel.channelKey)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
joinChannelPresence(channelKeyInput, options = {}) {
|
|
207
|
+
this.#ensureInitialized()
|
|
208
|
+
this.#assertChannelMember(channelKeyInput, options.ownerAddress)
|
|
209
|
+
const channel = this.#resolveChannel(channelKeyInput, options.ownerAddress)
|
|
210
|
+
const event = this.#upsertChannelPresenceSession(channel, {
|
|
211
|
+
...options,
|
|
212
|
+
address: options.ownerAddress,
|
|
213
|
+
local: true,
|
|
214
|
+
})
|
|
215
|
+
if (event) {
|
|
216
|
+
this.#broadcastChannelPresence(event)
|
|
217
|
+
}
|
|
218
|
+
return this.#getChannelPresenceList(channel.channelKey)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
heartbeatChannelPresence(channelKeyInput, options = {}) {
|
|
222
|
+
this.#ensureInitialized()
|
|
223
|
+
this.#assertChannelMember(channelKeyInput, options.ownerAddress)
|
|
224
|
+
const channel = this.#resolveChannel(channelKeyInput, options.ownerAddress)
|
|
225
|
+
const event = this.#touchChannelPresenceSession(channel, {
|
|
226
|
+
...options,
|
|
227
|
+
address: options.ownerAddress,
|
|
228
|
+
local: true,
|
|
229
|
+
})
|
|
230
|
+
this.#broadcastChannelPresence(
|
|
231
|
+
event ||
|
|
232
|
+
this.#formatChannelPresence(
|
|
233
|
+
channel.channelKey,
|
|
234
|
+
options.ownerAddress,
|
|
235
|
+
'heartbeat'
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
return this.#getChannelPresenceList(channel.channelKey)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
updateChannelPresenceProfile(channelKeyInput, options = {}) {
|
|
242
|
+
this.#ensureInitialized()
|
|
243
|
+
this.#assertChannelMember(channelKeyInput, options.ownerAddress)
|
|
244
|
+
const channel = this.#resolveChannel(channelKeyInput, options.ownerAddress)
|
|
245
|
+
const event = this.#updateChannelPresenceProfile(channel, {
|
|
246
|
+
...options,
|
|
247
|
+
address: options.ownerAddress,
|
|
248
|
+
local: true,
|
|
249
|
+
})
|
|
250
|
+
if (event) {
|
|
251
|
+
this.#broadcastChannelPresence(event)
|
|
252
|
+
}
|
|
253
|
+
return this.#getChannelPresenceList(channel.channelKey)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
leaveChannelPresence(channelKeyInput, options = {}) {
|
|
257
|
+
this.#ensureInitialized()
|
|
258
|
+
this.#assertChannelMember(channelKeyInput, options.ownerAddress)
|
|
259
|
+
const channel = this.#resolveChannel(channelKeyInput, options.ownerAddress)
|
|
260
|
+
const events = this.#removeChannelPresenceSessions(channel.channelKey, {
|
|
261
|
+
...options,
|
|
262
|
+
address: options.ownerAddress,
|
|
263
|
+
})
|
|
264
|
+
events.forEach(event => this.#broadcastChannelPresence(event))
|
|
265
|
+
return this.#getChannelPresenceList(channel.channelKey)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
clearChannelPresenceSource(sourceId, options = {}) {
|
|
269
|
+
this.#ensureInitialized()
|
|
270
|
+
const events = this.#removeChannelPresenceSessionsBySource(sourceId)
|
|
271
|
+
if (options.broadcast) {
|
|
272
|
+
events.forEach(event => this.#broadcastChannelPresence(event))
|
|
273
|
+
}
|
|
274
|
+
return events
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
pruneChannelPresence() {
|
|
278
|
+
this.#ensureInitialized()
|
|
279
|
+
return this.#pruneStaleChannelPresence()
|
|
280
|
+
}
|
|
281
|
+
|
|
193
282
|
/**
|
|
194
283
|
* 初始化引擎 — 必须在调用其他方法之前调用
|
|
195
284
|
*/
|
|
@@ -363,6 +452,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
363
452
|
console.log(`[MostBox] Engine initialized successfully`)
|
|
364
453
|
this.emit('ready')
|
|
365
454
|
this.#resumeHoldingsInBackground()
|
|
455
|
+
this.#startChannelPresenceSweeper()
|
|
366
456
|
|
|
367
457
|
return this
|
|
368
458
|
}
|
|
@@ -419,6 +509,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
419
509
|
this.#channelPeers.clear()
|
|
420
510
|
this.#channelCandidateCache.clear()
|
|
421
511
|
this.#channelStreams.clear()
|
|
512
|
+
this.#clearChannelPresenceRuntime()
|
|
422
513
|
this.#channels = []
|
|
423
514
|
|
|
424
515
|
if (this.#store) {
|
|
@@ -3111,6 +3202,312 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
3111
3202
|
)
|
|
3112
3203
|
}
|
|
3113
3204
|
|
|
3205
|
+
#normalizePresenceSessionId(sessionId) {
|
|
3206
|
+
return String(sessionId || 'default').trim().slice(0, 120) || 'default'
|
|
3207
|
+
}
|
|
3208
|
+
|
|
3209
|
+
#normalizePresenceSourceId(options = {}) {
|
|
3210
|
+
const sourceId = String(options.sourceId || '').trim()
|
|
3211
|
+
if (sourceId) return sourceId.slice(0, 160)
|
|
3212
|
+
const sourcePeerId = String(options.sourcePeerId || '').trim()
|
|
3213
|
+
if (sourcePeerId) return `peer:${sourcePeerId}`.slice(0, 180)
|
|
3214
|
+
return 'local'
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
#getPresenceSessionKey(options = {}) {
|
|
3218
|
+
return [
|
|
3219
|
+
this.#normalizePresenceSourceId(options),
|
|
3220
|
+
normalizeOwnerAddress(options.address),
|
|
3221
|
+
this.#normalizePresenceSessionId(options.sessionId),
|
|
3222
|
+
].join(':')
|
|
3223
|
+
}
|
|
3224
|
+
|
|
3225
|
+
#getChannelPresenceSessionMap(channelKey) {
|
|
3226
|
+
if (!this.#channelPresenceSessions.has(channelKey)) {
|
|
3227
|
+
this.#channelPresenceSessions.set(channelKey, new Map())
|
|
3228
|
+
}
|
|
3229
|
+
return this.#channelPresenceSessions.get(channelKey)
|
|
3230
|
+
}
|
|
3231
|
+
|
|
3232
|
+
#getChannelPresenceProfileMap(channelKey) {
|
|
3233
|
+
if (!this.#channelPresenceProfiles.has(channelKey)) {
|
|
3234
|
+
this.#channelPresenceProfiles.set(channelKey, new Map())
|
|
3235
|
+
}
|
|
3236
|
+
return this.#channelPresenceProfiles.get(channelKey)
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
#isChannelPresenceAddressOnline(channelKey, address) {
|
|
3240
|
+
const normalizedAddress = normalizeOwnerAddress(address)
|
|
3241
|
+
if (!normalizedAddress) return false
|
|
3242
|
+
const sessions = this.#channelPresenceSessions.get(channelKey)
|
|
3243
|
+
if (!sessions) return false
|
|
3244
|
+
return [...sessions.values()].some(
|
|
3245
|
+
session => session.address === normalizedAddress
|
|
3246
|
+
)
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
#getChannelPresenceList(channelKey) {
|
|
3250
|
+
const sessions = this.#channelPresenceSessions.get(channelKey)
|
|
3251
|
+
if (!sessions) return []
|
|
3252
|
+
const addresses = uniqueStrings(
|
|
3253
|
+
[...sessions.values()].map(session => session.address)
|
|
3254
|
+
)
|
|
3255
|
+
return addresses
|
|
3256
|
+
.map(address => this.#formatChannelPresence(channelKey, address))
|
|
3257
|
+
.filter(Boolean)
|
|
3258
|
+
}
|
|
3259
|
+
|
|
3260
|
+
#formatChannelPresence(channelKey, address, status = 'online') {
|
|
3261
|
+
const normalizedAddress = normalizeOwnerAddress(address)
|
|
3262
|
+
if (!normalizedAddress) return null
|
|
3263
|
+
const sessions = [
|
|
3264
|
+
...(this.#channelPresenceSessions.get(channelKey)?.values() || []),
|
|
3265
|
+
].filter(session => session.address === normalizedAddress)
|
|
3266
|
+
const profile = this.#channelPresenceProfiles
|
|
3267
|
+
.get(channelKey)
|
|
3268
|
+
?.get(normalizedAddress)
|
|
3269
|
+
const lastSeen = Math.max(
|
|
3270
|
+
0,
|
|
3271
|
+
Number(profile?.lastSeen) || 0,
|
|
3272
|
+
...sessions.map(session => Number(session.lastSeen) || 0)
|
|
3273
|
+
)
|
|
3274
|
+
return {
|
|
3275
|
+
channelKey,
|
|
3276
|
+
channelId:
|
|
3277
|
+
this.#channels.find(channel => channel.channelKey === channelKey)
|
|
3278
|
+
?.channelId || channelKey,
|
|
3279
|
+
address: normalizedAddress,
|
|
3280
|
+
displayName: profile?.displayName || undefined,
|
|
3281
|
+
avatar: profile?.avatar || undefined,
|
|
3282
|
+
profileUpdatedAt: profile?.profileUpdatedAt || undefined,
|
|
3283
|
+
lastSeen,
|
|
3284
|
+
online: sessions.length > 0,
|
|
3285
|
+
local: sessions.some(session => session.local),
|
|
3286
|
+
status,
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3290
|
+
#upsertChannelPresenceProfile(channelKey, address, options = {}, now = Date.now()) {
|
|
3291
|
+
const normalizedAddress = normalizeOwnerAddress(address)
|
|
3292
|
+
if (!normalizedAddress) return false
|
|
3293
|
+
const hasDisplayName = Object.prototype.hasOwnProperty.call(
|
|
3294
|
+
options,
|
|
3295
|
+
'displayName'
|
|
3296
|
+
)
|
|
3297
|
+
const hasAvatar = Object.prototype.hasOwnProperty.call(options, 'avatar')
|
|
3298
|
+
const profileUpdatedAt = Number(options.profileUpdatedAt)
|
|
3299
|
+
const hasProfileUpdatedAt =
|
|
3300
|
+
Number.isFinite(profileUpdatedAt) && profileUpdatedAt > 0
|
|
3301
|
+
if (!hasDisplayName && !hasAvatar && !hasProfileUpdatedAt) return false
|
|
3302
|
+
|
|
3303
|
+
const profiles = this.#getChannelPresenceProfileMap(channelKey)
|
|
3304
|
+
const previous = profiles.get(normalizedAddress)
|
|
3305
|
+
const nextUpdatedAt = hasProfileUpdatedAt
|
|
3306
|
+
? Math.floor(profileUpdatedAt)
|
|
3307
|
+
: now
|
|
3308
|
+
if (
|
|
3309
|
+
previous?.profileUpdatedAt &&
|
|
3310
|
+
hasProfileUpdatedAt &&
|
|
3311
|
+
nextUpdatedAt < previous.profileUpdatedAt
|
|
3312
|
+
) {
|
|
3313
|
+
return false
|
|
3314
|
+
}
|
|
3315
|
+
|
|
3316
|
+
const next = {
|
|
3317
|
+
address: normalizedAddress,
|
|
3318
|
+
displayName: previous?.displayName || '',
|
|
3319
|
+
avatar: previous?.avatar || '',
|
|
3320
|
+
profileUpdatedAt: nextUpdatedAt,
|
|
3321
|
+
lastSeen: now,
|
|
3322
|
+
}
|
|
3323
|
+
if (hasDisplayName) {
|
|
3324
|
+
next.displayName = normalizeChannelDisplayName(
|
|
3325
|
+
options.displayName,
|
|
3326
|
+
normalizedAddress
|
|
3327
|
+
)
|
|
3328
|
+
}
|
|
3329
|
+
if (hasAvatar) {
|
|
3330
|
+
next.avatar = normalizeChannelAvatar(options.avatar)
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
const changed =
|
|
3334
|
+
!previous ||
|
|
3335
|
+
previous.displayName !== next.displayName ||
|
|
3336
|
+
previous.avatar !== next.avatar ||
|
|
3337
|
+
previous.profileUpdatedAt !== next.profileUpdatedAt
|
|
3338
|
+
profiles.set(normalizedAddress, next)
|
|
3339
|
+
return changed
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
#emitChannelPresence(channelKey, address, status) {
|
|
3343
|
+
const event = this.#formatChannelPresence(channelKey, address, status)
|
|
3344
|
+
if (event) {
|
|
3345
|
+
this.emit('channel:presence', event)
|
|
3346
|
+
}
|
|
3347
|
+
return event
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
#upsertChannelPresenceSession(channel, options = {}) {
|
|
3351
|
+
const address = normalizeOwnerAddress(options.address)
|
|
3352
|
+
if (!address) return null
|
|
3353
|
+
const now = Number(options.lastSeen) || Date.now()
|
|
3354
|
+
const channelKey = channel.channelKey
|
|
3355
|
+
const wasOnline = this.#isChannelPresenceAddressOnline(channelKey, address)
|
|
3356
|
+
const session = {
|
|
3357
|
+
sessionId: this.#normalizePresenceSessionId(options.sessionId),
|
|
3358
|
+
sourceId: this.#normalizePresenceSourceId(options),
|
|
3359
|
+
address,
|
|
3360
|
+
channelKey,
|
|
3361
|
+
lastSeen: now,
|
|
3362
|
+
local: options.local === true,
|
|
3363
|
+
sourcePeerId: String(options.sourcePeerId || '').trim(),
|
|
3364
|
+
}
|
|
3365
|
+
this.#getChannelPresenceSessionMap(channelKey).set(
|
|
3366
|
+
this.#getPresenceSessionKey(session),
|
|
3367
|
+
session
|
|
3368
|
+
)
|
|
3369
|
+
const profileChanged = this.#upsertChannelPresenceProfile(
|
|
3370
|
+
channelKey,
|
|
3371
|
+
address,
|
|
3372
|
+
options,
|
|
3373
|
+
now
|
|
3374
|
+
)
|
|
3375
|
+
if (!wasOnline) {
|
|
3376
|
+
return this.#emitChannelPresence(channelKey, address, 'online')
|
|
3377
|
+
}
|
|
3378
|
+
if (profileChanged) {
|
|
3379
|
+
return this.#emitChannelPresence(channelKey, address, 'profile')
|
|
3380
|
+
}
|
|
3381
|
+
return null
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
#touchChannelPresenceSession(channel, options = {}) {
|
|
3385
|
+
const address = normalizeOwnerAddress(options.address)
|
|
3386
|
+
if (!address) return null
|
|
3387
|
+
const channelKey = channel.channelKey
|
|
3388
|
+
const sessionKey = this.#getPresenceSessionKey({
|
|
3389
|
+
...options,
|
|
3390
|
+
address,
|
|
3391
|
+
})
|
|
3392
|
+
const sessions = this.#getChannelPresenceSessionMap(channelKey)
|
|
3393
|
+
const existing = sessions.get(sessionKey)
|
|
3394
|
+
if (!existing) {
|
|
3395
|
+
return this.#upsertChannelPresenceSession(channel, {
|
|
3396
|
+
...options,
|
|
3397
|
+
address,
|
|
3398
|
+
})
|
|
3399
|
+
}
|
|
3400
|
+
existing.lastSeen = Number(options.lastSeen) || Date.now()
|
|
3401
|
+
sessions.set(sessionKey, existing)
|
|
3402
|
+
return null
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
#updateChannelPresenceProfile(channel, options = {}) {
|
|
3406
|
+
const address = normalizeOwnerAddress(options.address)
|
|
3407
|
+
if (!address) return null
|
|
3408
|
+
const now = Number(options.lastSeen) || Date.now()
|
|
3409
|
+
const changed = this.#upsertChannelPresenceProfile(
|
|
3410
|
+
channel.channelKey,
|
|
3411
|
+
address,
|
|
3412
|
+
options,
|
|
3413
|
+
now
|
|
3414
|
+
)
|
|
3415
|
+
if (changed && this.#isChannelPresenceAddressOnline(channel.channelKey, address)) {
|
|
3416
|
+
return this.#emitChannelPresence(channel.channelKey, address, 'profile')
|
|
3417
|
+
}
|
|
3418
|
+
return null
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
#removeChannelPresenceSessions(channelKey, options = {}) {
|
|
3422
|
+
const address = normalizeOwnerAddress(options.address)
|
|
3423
|
+
const sourceId = this.#normalizePresenceSourceId(options)
|
|
3424
|
+
const sessionId = options.sessionId
|
|
3425
|
+
? this.#normalizePresenceSessionId(options.sessionId)
|
|
3426
|
+
: ''
|
|
3427
|
+
const sessions = this.#channelPresenceSessions.get(channelKey)
|
|
3428
|
+
if (!sessions || (!address && !sourceId)) return []
|
|
3429
|
+
|
|
3430
|
+
const touchedAddresses = new Set()
|
|
3431
|
+
for (const [key, session] of [...sessions]) {
|
|
3432
|
+
if (address && session.address !== address) continue
|
|
3433
|
+
if (sourceId && session.sourceId !== sourceId) continue
|
|
3434
|
+
if (sessionId && session.sessionId !== sessionId) continue
|
|
3435
|
+
touchedAddresses.add(session.address)
|
|
3436
|
+
sessions.delete(key)
|
|
3437
|
+
}
|
|
3438
|
+
if (sessions.size === 0) {
|
|
3439
|
+
this.#channelPresenceSessions.delete(channelKey)
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
return [...touchedAddresses]
|
|
3443
|
+
.filter(item => !this.#isChannelPresenceAddressOnline(channelKey, item))
|
|
3444
|
+
.map(item => this.#emitChannelPresence(channelKey, item, 'offline'))
|
|
3445
|
+
.filter(Boolean)
|
|
3446
|
+
}
|
|
3447
|
+
|
|
3448
|
+
#removeChannelPresenceSessionsBySource(sourceId) {
|
|
3449
|
+
const normalizedSourceId = String(sourceId || '').trim()
|
|
3450
|
+
if (!normalizedSourceId) return []
|
|
3451
|
+
const events = []
|
|
3452
|
+
for (const [channelKey, sessions] of [...this.#channelPresenceSessions]) {
|
|
3453
|
+
const touchedAddresses = new Set()
|
|
3454
|
+
for (const [key, session] of [...sessions]) {
|
|
3455
|
+
if (session.sourceId !== normalizedSourceId) continue
|
|
3456
|
+
touchedAddresses.add(session.address)
|
|
3457
|
+
sessions.delete(key)
|
|
3458
|
+
}
|
|
3459
|
+
if (sessions.size === 0) {
|
|
3460
|
+
this.#channelPresenceSessions.delete(channelKey)
|
|
3461
|
+
}
|
|
3462
|
+
for (const address of touchedAddresses) {
|
|
3463
|
+
if (!this.#isChannelPresenceAddressOnline(channelKey, address)) {
|
|
3464
|
+
const event = this.#emitChannelPresence(channelKey, address, 'offline')
|
|
3465
|
+
if (event) events.push(event)
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
return events
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3472
|
+
#pruneStaleChannelPresence(now = Date.now()) {
|
|
3473
|
+
const events = []
|
|
3474
|
+
for (const [channelKey, sessions] of [...this.#channelPresenceSessions]) {
|
|
3475
|
+
const touchedAddresses = new Set()
|
|
3476
|
+
for (const [key, session] of [...sessions]) {
|
|
3477
|
+
if (now - session.lastSeen <= CHANNEL_PRESENCE_TIMEOUT_MS) continue
|
|
3478
|
+
touchedAddresses.add(session.address)
|
|
3479
|
+
sessions.delete(key)
|
|
3480
|
+
}
|
|
3481
|
+
if (sessions.size === 0) {
|
|
3482
|
+
this.#channelPresenceSessions.delete(channelKey)
|
|
3483
|
+
}
|
|
3484
|
+
for (const address of touchedAddresses) {
|
|
3485
|
+
if (!this.#isChannelPresenceAddressOnline(channelKey, address)) {
|
|
3486
|
+
const event = this.#emitChannelPresence(channelKey, address, 'offline')
|
|
3487
|
+
if (event) events.push(event)
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
return events
|
|
3492
|
+
}
|
|
3493
|
+
|
|
3494
|
+
#startChannelPresenceSweeper() {
|
|
3495
|
+
if (this.#channelPresenceSweepTimer) return
|
|
3496
|
+
this.#channelPresenceSweepTimer = setInterval(() => {
|
|
3497
|
+
this.#pruneStaleChannelPresence()
|
|
3498
|
+
}, CHANNEL_PRESENCE_HEARTBEAT_MS)
|
|
3499
|
+
this.#channelPresenceSweepTimer.unref?.()
|
|
3500
|
+
}
|
|
3501
|
+
|
|
3502
|
+
#clearChannelPresenceRuntime() {
|
|
3503
|
+
if (this.#channelPresenceSweepTimer) {
|
|
3504
|
+
clearInterval(this.#channelPresenceSweepTimer)
|
|
3505
|
+
this.#channelPresenceSweepTimer = null
|
|
3506
|
+
}
|
|
3507
|
+
this.#channelPresenceSessions.clear()
|
|
3508
|
+
this.#channelPresenceProfiles.clear()
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3114
3511
|
async #getNextChannelMessageTimestamp(channelKey) {
|
|
3115
3512
|
const coresMap = this.#channelCores.get(channelKey)
|
|
3116
3513
|
let maxTimestamp = 0
|
|
@@ -4473,6 +4870,40 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
4473
4870
|
}
|
|
4474
4871
|
}
|
|
4475
4872
|
|
|
4873
|
+
#sendChannelPresence(stream, event) {
|
|
4874
|
+
if (!stream || stream.destroyed || stream.writableEnded || !event) {
|
|
4875
|
+
this.#channelStreams.delete(stream)
|
|
4876
|
+
return false
|
|
4877
|
+
}
|
|
4878
|
+
try {
|
|
4879
|
+
stream.write(
|
|
4880
|
+
`${JSON.stringify({
|
|
4881
|
+
type: 'channel-presence',
|
|
4882
|
+
peerId: this.getNodeId(),
|
|
4883
|
+
channelId: event.channelId,
|
|
4884
|
+
channelKey: event.channelKey,
|
|
4885
|
+
address: event.address,
|
|
4886
|
+
status: event.status,
|
|
4887
|
+
displayName: event.displayName,
|
|
4888
|
+
avatar: event.avatar,
|
|
4889
|
+
profileUpdatedAt: event.profileUpdatedAt,
|
|
4890
|
+
lastSeen: event.lastSeen || Date.now(),
|
|
4891
|
+
sessionId: event.sessionId || 'default',
|
|
4892
|
+
})}\n`
|
|
4893
|
+
)
|
|
4894
|
+
return true
|
|
4895
|
+
} catch {
|
|
4896
|
+
this.#channelStreams.delete(stream)
|
|
4897
|
+
return false
|
|
4898
|
+
}
|
|
4899
|
+
}
|
|
4900
|
+
|
|
4901
|
+
#broadcastChannelPresence(event) {
|
|
4902
|
+
for (const stream of [...this.#channelStreams]) {
|
|
4903
|
+
this.#sendChannelPresence(stream, event)
|
|
4904
|
+
}
|
|
4905
|
+
}
|
|
4906
|
+
|
|
4476
4907
|
async #processChannelHelloMessage(msg) {
|
|
4477
4908
|
if (msg.type !== 'channel-hello') return null
|
|
4478
4909
|
|
|
@@ -4554,6 +4985,45 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
4554
4985
|
return msg.peerId
|
|
4555
4986
|
}
|
|
4556
4987
|
|
|
4988
|
+
#processChannelPresenceMessage(msg) {
|
|
4989
|
+
if (msg.type !== 'channel-presence') return null
|
|
4990
|
+
const peerId = String(msg.peerId || '').trim()
|
|
4991
|
+
if (!peerId || peerId === this.getNodeId()) return null
|
|
4992
|
+
const channelId = normalizeChannelId(msg.channelId || msg.channelKey)
|
|
4993
|
+
const channelKey = buildChannelKey(channelId)
|
|
4994
|
+
const localChannel = this.#channels.find(
|
|
4995
|
+
channel => channel.channelKey === channelKey
|
|
4996
|
+
)
|
|
4997
|
+
if (!localChannel) return peerId
|
|
4998
|
+
|
|
4999
|
+
const address = normalizeOwnerAddress(msg.address)
|
|
5000
|
+
if (!address) return peerId
|
|
5001
|
+
|
|
5002
|
+
const status = String(msg.status || '').trim()
|
|
5003
|
+
const options = {
|
|
5004
|
+
address,
|
|
5005
|
+
sessionId: msg.sessionId,
|
|
5006
|
+
sourcePeerId: peerId,
|
|
5007
|
+
local: false,
|
|
5008
|
+
displayName: msg.displayName,
|
|
5009
|
+
avatar: msg.avatar,
|
|
5010
|
+
profileUpdatedAt: msg.profileUpdatedAt,
|
|
5011
|
+
lastSeen: Number(msg.lastSeen) || Date.now(),
|
|
5012
|
+
}
|
|
5013
|
+
|
|
5014
|
+
if (status === 'online') {
|
|
5015
|
+
this.#upsertChannelPresenceSession(localChannel, options)
|
|
5016
|
+
} else if (status === 'heartbeat') {
|
|
5017
|
+
this.#touchChannelPresenceSession(localChannel, options)
|
|
5018
|
+
} else if (status === 'profile') {
|
|
5019
|
+
this.#updateChannelPresenceProfile(localChannel, options)
|
|
5020
|
+
} else if (status === 'offline') {
|
|
5021
|
+
this.#removeChannelPresenceSessions(localChannel.channelKey, options)
|
|
5022
|
+
}
|
|
5023
|
+
|
|
5024
|
+
return peerId
|
|
5025
|
+
}
|
|
5026
|
+
|
|
4557
5027
|
async #handleChannelConnection(conn) {
|
|
4558
5028
|
const stream = conn
|
|
4559
5029
|
let connectedPeerId = null
|
|
@@ -4572,9 +5042,11 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
4572
5042
|
newlineIndex = readBuffer.indexOf('\n')
|
|
4573
5043
|
if (!line) continue
|
|
4574
5044
|
try {
|
|
4575
|
-
const
|
|
4576
|
-
|
|
4577
|
-
|
|
5045
|
+
const message = JSON.parse(line)
|
|
5046
|
+
const peerId =
|
|
5047
|
+
message.type === 'channel-presence'
|
|
5048
|
+
? this.#processChannelPresenceMessage(message)
|
|
5049
|
+
: await this.#processChannelHelloMessage(message)
|
|
4578
5050
|
if (peerId) connectedPeerId = peerId
|
|
4579
5051
|
} catch (err) {
|
|
4580
5052
|
console.warn(`[MostBox] Failed to process channel data:`, err.message)
|
|
@@ -4603,6 +5075,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
4603
5075
|
})
|
|
4604
5076
|
}
|
|
4605
5077
|
}
|
|
5078
|
+
this.#removeChannelPresenceSessionsBySource(`peer:${connectedPeerId}`)
|
|
4606
5079
|
}
|
|
4607
5080
|
}
|
|
4608
5081
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
._page_1yyew_1{background:radial-gradient(circle at 35% 25%, #34c75924, transparent 32%), radial-gradient(circle at 78% 18%, #ff95001f, transparent 28%), var(--bg-primary);flex:1;min-height:0;padding:20px;overflow:auto}._headerActions_1yyew_12{align-items:center;gap:8px;display:flex}._entry_1yyew_18{grid-template-columns:minmax(0,1fr) 360px;align-items:center;gap:48px;max-width:1040px;min-height:calc(100vh - 116px);margin:0 auto;display:grid}._entryBrand_1yyew_28{align-items:center;gap:22px;display:flex}._cardMark_1yyew_34{width:112px;height:152px;box-shadow:var(--shadow-lg);color:#fff;background:linear-gradient(135deg,#1f8f5f,#284f7a);border:8px solid #fff;border-radius:8px;flex:0 0 112px;place-items:center;display:grid}._entryBrand_1yyew_28 h1{color:var(--text-primary);margin:0;font-size:64px;line-height:.95}._entryBrand_1yyew_28 p{max-width:520px;color:var(--text-secondary);margin:18px 0 0;font-size:18px;line-height:1.7}._entryPanel_1yyew_62{border:1px solid var(--glass-border);background:var(--glass-bg);box-shadow:var(--glass-shadow);border-radius:8px;gap:16px;padding:22px;display:grid}._accountLine_1yyew_72{color:var(--text-secondary);justify-content:space-between;align-items:center;gap:10px;font-size:14px;display:flex}._accountLine_1yyew_72 strong{max-width:190px;color:var(--text-primary);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}._joinForm_1yyew_89,._joinForm_1yyew_89 label{gap:8px;display:grid}._joinForm_1yyew_89 label{color:var(--text-secondary);font-size:14px}._joinForm_1yyew_89 input{border:1px solid var(--glass-border);background:var(--bg-primary);width:100%;min-height:40px;color:var(--text-primary);border-radius:8px;padding:0 12px}._status_1yyew_110{color:var(--text-secondary);margin:0;font-size:14px}._board_1yyew_116{grid-template-columns:minmax(0,1fr) minmax(320px,380px);gap:18px;min-height:100%;display:grid}._table_1yyew_123{background:linear-gradient(145deg, #0a533ee6, #163550eb), var(--glass-bg);min-height:560px;box-shadow:var(--shadow-lg);border:1px solid #ffffff2e;border-radius:8px;position:relative;overflow:hidden}._table_1yyew_123:before{content:"";pointer-events:none;border:2px solid #ffffff2e;border-radius:8px;position:absolute;inset:24px}._tableCenter_1yyew_144{color:#fff;background:#00000038;border:1px solid #ffffff38;border-radius:50%;flex-direction:column;justify-content:center;align-items:center;gap:5px;width:150px;height:150px;display:flex;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);box-shadow:inset 0 0 24px #00000029}._tableCenter_1yyew_144 span,._tableCenter_1yyew_144 small{opacity:.78;font-size:12px}._tableCenter_1yyew_144 strong{font-size:32px}._finishBanner_1yyew_173{z-index:2;color:#fff;white-space:nowrap;background:linear-gradient(135deg,#30d158e6,#34c759e6);border-radius:20px;align-items:center;gap:8px;padding:10px 20px;font-size:15px;font-weight:700;display:flex;position:absolute;top:16px;left:50%;transform:translate(-50%);box-shadow:0 4px 16px #30d15866}._finishBannerIcon_1yyew_192{font-size:20px}._seats_1yyew_196{z-index:1;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;padding:34px;display:grid;position:relative}._seat_1yyew_196{color:#fff;background:#ffffff1a;border:1px solid #ffffff29;border-radius:8px;grid-template-columns:44px minmax(0,1fr);align-items:center;gap:10px;min-height:88px;padding:12px;display:grid}._seatTurn_1yyew_218{border-color:#ffd666eb;box-shadow:0 0 0 2px #ffd6662e}._seatWinner_1yyew_223{border-color:#30d158eb;box-shadow:0 0 0 2px #30d15833}._seatFolded_1yyew_228{opacity:.5}._seatAvatar_1yyew_232{color:#fff;background:#17211b;border-radius:8px;place-items:center;width:44px;height:44px;font-size:15px;font-weight:900;display:grid;overflow:hidden}._seatAvatar_1yyew_232 img{object-fit:cover;width:100%;height:100%}._seatMain_1yyew_251,._seatMeta_1yyew_252{flex-direction:column;gap:4px;min-width:0;display:flex}._seatMain_1yyew_251 strong,._seatMain_1yyew_251 span,._seatMeta_1yyew_252 span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}._seatMain_1yyew_251 strong{font-size:14px}._seatMain_1yyew_251 span,._seatMeta_1yyew_252{opacity:.76;font-size:12px}._seatMeta_1yyew_252{flex-direction:row;grid-column:1/-1;justify-content:space-between}._panel_1yyew_283{flex-direction:column;gap:14px;min-width:0;display:flex}._panelSection_1yyew_290{border:1px solid var(--glass-border);background:var(--glass-bg);-webkit-backdrop-filter:var(--blur-md);box-shadow:var(--glass-shadow);border-radius:8px;padding:16px}._panelTitle_1yyew_300{color:var(--text-primary);align-items:center;gap:8px;margin-bottom:12px;font-size:14px;font-weight:600;display:flex}._hand_1yyew_310{grid-template-columns:repeat(3,62px);justify-content:center;gap:10px;display:grid}._card_1yyew_34{color:#17211b;text-align:left;background:#fff;border:1px solid #dbe1dc;border-radius:8px;grid-template-rows:auto 1fr;width:62px;height:92px;padding:7px;display:grid;box-shadow:0 8px 18px #0e16111f}._card_1yyew_34 span{font-size:15px;font-weight:900}._card_1yyew_34 i{place-self:end;font-size:22px;font-style:normal;font-weight:900}._red_1yyew_344{color:#c93d32}._black_1yyew_348{color:#17211b}._cardBack_1yyew_352{color:#fff;background:repeating-linear-gradient(45deg,#ffffff1f 0 6px,#0000 6px 12px),linear-gradient(135deg,#243f7a,#752f4d);border:1px solid #dbe1dc;border-radius:8px;justify-content:center;align-items:center;width:62px;height:92px;display:flex;box-shadow:0 8px 18px #0e16111f}._handLabel_1yyew_373{text-align:center;color:var(--text-secondary);margin-top:10px;font-size:13px}._actionGrid_1yyew_380,._raiseRow_1yyew_381{grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-top:10px;display:grid}._raiseRow_1yyew_381{grid-template-columns:minmax(0,1fr) 96px}._statusGrid_1yyew_392{grid-template-columns:76px minmax(0,1fr);align-items:start;gap:8px 12px;font-size:13px;display:grid}._statusGrid_1yyew_392 span{color:var(--text-secondary)}._statusGrid_1yyew_392 strong{color:var(--text-primary);overflow-wrap:anywhere;font-weight:600}._muted_1yyew_410{color:var(--text-secondary);margin:0;font-size:13px;line-height:1.6}._compareOverlay_1yyew_417{z-index:100;-webkit-backdrop-filter:blur(4px);background:#0009;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}._compareBox_1yyew_429{background:var(--glass-bg);border:1px solid var(--glass-border);box-shadow:var(--shadow-lg);border-radius:16px;flex-direction:column;align-items:center;gap:20px;width:90%;max-width:560px;padding:28px 36px;display:flex}._compareTitle_1yyew_443{color:var(--text-primary);font-size:18px;font-weight:700}._comparePlayers_1yyew_449{align-items:center;gap:20px;width:100%;display:flex}._compareSide_1yyew_456{background:#ffffff0d;border:2px solid #0000;border-radius:12px;flex-direction:column;flex:1;align-items:center;gap:12px;padding:16px;display:flex}._compareWinner_1yyew_468{background:#30d1581a;border-color:#30d158cc}._compareName_1yyew_473{color:var(--text-primary);text-align:center;text-overflow:ellipsis;white-space:nowrap;max-width:100%;font-size:14px;font-weight:600;overflow:hidden}._compareCards_1yyew_484{justify-content:center;gap:8px;display:flex}._compareCards_1yyew_484 ._card_1yyew_34{width:52px;height:76px;padding:5px}._compareCards_1yyew_484 ._card_1yyew_34 span{font-size:13px}._compareCards_1yyew_484 ._card_1yyew_34 i{font-size:18px}._compareCards_1yyew_484 ._cardBack_1yyew_352{width:52px;height:76px}._compareLabel_1yyew_509{color:var(--text-secondary);font-size:12px;font-weight:500}._compareVs_1yyew_515{color:var(--text-secondary);opacity:.6;font-size:20px;font-weight:900}._compareResult_1yyew_522{color:#30d158;font-size:16px;font-weight:700}@media (width<=980px){._entry_1yyew_18{grid-template-columns:1fr;align-content:center}._board_1yyew_116{grid-template-columns:1fr}._table_1yyew_123{min-height:420px}}@media (width<=640px){._page_1yyew_1{padding:12px}._entryBrand_1yyew_28{flex-direction:column;align-items:flex-start}._entryBrand_1yyew_28 h1{font-size:42px}._cardMark_1yyew_34{border-width:6px;flex-basis:100px;width:74px;height:100px}._seats_1yyew_196{grid-template-columns:1fr;padding:22px}._tableCenter_1yyew_144{margin:24px auto 0;position:static;transform:none}._table_1yyew_123:before{inset:14px}._hand_1yyew_310{grid-template-columns:repeat(3,minmax(58px,72px))}._actionGrid_1yyew_380,._raiseRow_1yyew_381{grid-template-columns:1fr}}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
._page_p7qf6_1{background:var(--bg-primary);min-height:calc(100vh - 64px);padding:18px}._headerActions_p7qf6_7{align-items:center;gap:8px;display:flex}._entry_p7qf6_13{grid-template-columns:minmax(0,1fr) 360px;align-items:center;gap:48px;max-width:1040px;min-height:calc(100vh - 116px);margin:0 auto;display:grid}._entryBrand_p7qf6_23{align-items:center;gap:22px;display:flex}._cardMark_p7qf6_29{width:112px;height:152px;box-shadow:var(--shadow-lg);color:#fff;background:#c93d32;border:8px solid #fff;border-radius:8px;flex:0 0 112px;place-items:center;font-size:70px;font-weight:900;display:grid}._entryBrand_p7qf6_23 h1{color:var(--text-primary);margin:0;font-size:64px;line-height:.95}._entryBrand_p7qf6_23 p{max-width:520px;color:var(--text-secondary);margin:18px 0 0;font-size:18px;line-height:1.7}._entryPanel_p7qf6_59,._panel_p7qf6_60,._handPanel_p7qf6_61{border:1px solid var(--glass-border);background:var(--glass-bg);box-shadow:var(--glass-shadow);border-radius:8px}._entryPanel_p7qf6_59{gap:16px;padding:22px;display:grid}._accountLine_p7qf6_74,._scoreMeta_p7qf6_75,._roomBar_p7qf6_76,._badges_p7qf6_77,._actions_p7qf6_78{align-items:center;gap:10px;display:flex}._accountLine_p7qf6_74{color:var(--text-secondary);justify-content:space-between;font-size:14px}._accountLine_p7qf6_74 strong{max-width:190px;color:var(--text-primary);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}._joinForm_p7qf6_98,._joinForm_p7qf6_98 label{gap:8px;display:grid}._joinForm_p7qf6_98 label{color:var(--text-secondary);font-size:14px}._joinForm_p7qf6_98 input{border:1px solid var(--glass-border);background:var(--bg-primary);width:100%;min-height:40px;color:var(--text-primary);border-radius:8px;padding:0 12px}._status_p7qf6_119{color:var(--text-secondary);margin:0;font-size:14px}._gameGrid_p7qf6_125{grid-template:"table side"minmax(360px,1fr)"hand side"/minmax(0,1fr) 320px;gap:14px;min-height:calc(100vh - 104px);display:grid}._tablePanel_p7qf6_136{color:#fff;background:radial-gradient(circle,#ffffff2e,#0000 46%),#247658;border-radius:8px;grid-area:table;grid-template-rows:auto auto 1fr auto;gap:14px;min-height:360px;padding:16px;display:grid}._roomBar_p7qf6_76{justify-content:space-between}._roomBar_p7qf6_76 div:first-child{gap:2px;display:grid}._roomBar_p7qf6_76 span,._badges_p7qf6_77 span{color:#ffffffc7;font-size:13px}._badges_p7qf6_77{flex-wrap:wrap;justify-content:flex-end}._seats_p7qf6_170{grid-template-columns:repeat(auto-fit,minmax(168px,1fr));gap:10px;display:grid}._player_p7qf6_176{color:#17211b;background:#ffffffeb;border:1px solid #17211b1f;border-radius:8px;align-items:center;gap:10px;min-width:0;padding:8px;display:flex}._tablePanel_p7qf6_136 ._player_p7qf6_176{color:#fff;background:#ffffff2e}._active_p7qf6_193{outline:3px solid #f3cf5a}._winner_p7qf6_197{outline:3px solid #7be39e}._avatar_p7qf6_201{color:#fff;background:#17211b;border-radius:50%;flex:0 0 40px;place-items:center;width:40px;height:40px;font-size:13px;font-weight:900;display:grid;overflow:hidden}._avatar_p7qf6_201 img{object-fit:cover;width:100%;height:100%}._player_p7qf6_176 strong,._player_p7qf6_176 span{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}._player_p7qf6_176 span{opacity:.76;max-width:190px;font-size:12px}._centerTable_p7qf6_235{grid-template-columns:120px minmax(0,1fr) 120px;align-items:center;gap:14px;display:grid}._deckBox_p7qf6_242,._playedBox_p7qf6_243{text-align:center;background:#0b221942;border-radius:8px}._deckBox_p7qf6_242{gap:6px;padding:18px 8px;display:grid}._deckBox_p7qf6_242 span{opacity:.75;font-size:13px}._deckBox_p7qf6_242 strong{font-size:30px}._playedBox_p7qf6_243{place-items:center;min-height:150px;padding:16px;display:grid}._playedCards_p7qf6_271{flex-wrap:wrap;justify-content:center;gap:8px;margin-top:10px;display:flex}._notice_p7qf6_279{text-align:center;background:#0000002e;border-radius:8px;padding:10px 12px;font-weight:800}._resultDetail_p7qf6_287{gap:4px;font-weight:600;display:grid}._resultDetail_p7qf6_287 p{margin:0;font-size:14px}._sidePanel_p7qf6_298{grid-area:side;align-content:start;gap:12px;display:grid}._panel_p7qf6_60{gap:12px;padding:14px;display:grid}._panel_p7qf6_60 h3{color:var(--text-primary);margin:0;font-size:15px}._scoreMeta_p7qf6_75{color:var(--text-secondary);justify-content:space-between;font-size:13px}._scoreRow_p7qf6_323{border-top:1px solid var(--glass-border);color:var(--text-primary);justify-content:space-between;padding-top:8px;display:flex}._logPanel_p7qf6_331{max-height:280px;overflow:auto}._logPanel_p7qf6_331 p{color:var(--text-secondary);margin:0;font-size:13px;line-height:1.5}._handPanel_p7qf6_61{grid-area:hand;gap:12px;padding:12px;display:grid}._hand_p7qf6_61{gap:8px;min-height:118px;padding:4px 2px 10px;display:flex;overflow-x:auto}._cardButton_p7qf6_358{background:0 0;border:0;border-radius:8px;flex:none;min-height:auto;padding:0;transition:transform .16s}._picked_p7qf6_368{transform:translateY(-14px)}._card_p7qf6_29{color:#17211b;text-align:left;background:#fff;border:1px solid #dbe1dc;border-radius:8px;grid-template-rows:auto 1fr;width:62px;height:92px;padding:7px;display:grid;box-shadow:0 8px 18px #0e16111f}._card_p7qf6_29 span{font-size:15px;font-weight:900}._card_p7qf6_29 i{place-self:end;font-size:22px;font-style:normal;font-weight:900}._red_p7qf6_399{color:#c93d32}._black_p7qf6_403{color:#17211b}._small_p7qf6_407{width:46px;height:66px;padding:5px}._small_p7qf6_407 span{font-size:12px}._small_p7qf6_407 i{font-size:15px}._actions_p7qf6_78{flex-wrap:wrap}._preview_p7qf6_425{border:1px solid var(--glass-border);background:var(--bg-primary);min-height:46px;color:var(--text-secondary);border-radius:8px;flex:260px;padding:7px 10px;display:grid}._preview_p7qf6_425 strong{color:var(--text-primary);font-size:14px}._preview_p7qf6_425 span{text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}._valid_p7qf6_448{border-color:#176b4d70}@media (width<=980px){._entry_p7qf6_13{grid-template-columns:1fr;align-content:center}._gameGrid_p7qf6_125{grid-template-columns:1fr;grid-template-areas:"table""hand""side"}}@media (width<=640px){._page_p7qf6_1{padding:10px}._entryBrand_p7qf6_23{flex-direction:column;align-items:flex-start}._entryBrand_p7qf6_23 h1{font-size:42px}._cardMark_p7qf6_29{border-width:6px;flex-basis:100px;width:74px;height:100px;font-size:46px}._centerTable_p7qf6_235{grid-template-columns:1fr}._actions_p7qf6_78>button{flex:110px}}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{n as e,o as t,t as n}from"./jsx-runtime-Bt-cYkS5.js";import{C as r,T as i,d as a,l as o,s,u as c}from"./ui-BpDGD3IR.js";import{a as l}from"./i18n-NjvOSuV6.js";import{n as u,r as d}from"./LanguageToggle-CSQtRZ3A.js";import{t as f}from"./copy-DEzFUzeX.js";import{n as p}from"./SidebarHomeLink-DXv0c9Ob.js";import{t as m}from"./play-B-BjsTZz.js";import{a as h,i as g,n as _,r as ee,t as te}from"./useGameRoom-cs9YpYt7.js";import{E as v,N as ne,S as y,b as re,c as b,i as ie,w as x,x as S}from"./index-BI8oNSVC.js";var ae=r(`refresh-ccw`,[[`path`,{d:`M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8`,key:`14sxne`}],[`path`,{d:`M3 3v5h5`,key:`1xhq8a`}],[`path`,{d:`M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16`,key:`1hlbsb`}],[`path`,{d:`M16 16h5v5`,key:`ccwih5`}]]),C=t(e(),1),oe=[10,20,50,100],w=[`2`,`3`,`4`,`5`,`6`,`7`,`8`,`9`,`10`,`J`,`Q`,`K`,`A`],T=[`D`,`C`,`H`,`S`],E=new Map(w.map((e,t)=>[e,t+2])),D=new Map(T.map((e,t)=>[e,t+1])),O={high:1,pair:2,straight:3,flush:4,straightFlush:5,triple:6},se={[O.high]:`散牌`,[O.pair]:`对子`,[O.straight]:`顺子`,[O.flush]:`金花`,[O.straightFlush]:`顺金`,[O.triple]:`豹子`};function k(e){return x(e)}function ce(e=`zhajinhua`){return re(e)}function le(){return ce(`round`)}function ue(){let e=[];for(let t of T)for(let n of w)e.push(`${n}${t}`);return e}function A(e=ue(),t=Math.random){let n=[...e];for(let e=n.length-1;e>0;e--){let r=Math.floor(t()*(e+1));[n[e],n[r]]=[n[r],n[e]]}return n}function j(e){let t=String(e||``).trim().toUpperCase(),n=t.slice(-1),r=t.slice(0,-1);if(!E.has(r)||!D.has(n))throw Error(`Invalid card: ${e}`);return{id:`${r}${n}`,rank:r,suit:n,rankValue:E.get(r),suitValue:D.get(n)}}function M(e,t){let n=Math.max(e.length,t.length);for(let r=0;r<n;r++){let n=e[r]||0,i=t[r]||0;if(n!==i)return n-i}return 0}function N(e){let t=[...new Set(e)].sort((e,t)=>e-t);return t.length===3?t[0]===2&&t[1]===3&&t[2]===14?3:t[0]+1===t[1]&&t[1]+1===t[2]?t[2]:0:0}function P(e){if(!Array.isArray(e)||e.length!==3)throw Error(`炸金花手牌必须是 3 张牌`);let t=e.map(j),n=t.map(e=>e.rankValue),r=new Map;for(let e of n)r.set(e,(r.get(e)||0)+1);let i=new Set(t.map(e=>e.suit)).size===1,a=N(n),o=[...n].sort((e,t)=>t-e),s=Math.max(...t.map(e=>e.suitValue)),c=O.high,l=o;return r.size===1?(c=O.triple,l=[o[0]]):a&&i?(c=O.straightFlush,l=[a]):i?(c=O.flush,l=o):a?(c=O.straight,l=[a]):r.size===2&&(c=O.pair,l=[[...r.entries()].find(([,e])=>e===2)[0],[...r.entries()].find(([,e])=>e===1)[0]]),{category:c,label:se[c],tiebreakers:l,tieSuit:s,cards:t.map(e=>e.id)}}function F(e,t){let n=P(e),r=P(t);if(n.category!==r.category)return n.category-r.category;let i=M(n.tiebreakers,r.tiebreakers);return i===0?n.tieSuit-r.tieSuit:i}function de(e){return P(e).label}function I({roundId:e,action:t,amount:n,target:r}){return{roundId:e,eventId:ce(`action`),action:t,amount:n,target:r}}function fe(e=[]){let t=e.filter(e=>Number(e.chips)>=10);return t.length>=2&&t.length<=5}function pe({roomCode:e,players:t,hostAddress:n,previousSeq:r=0,previousWinner:i=``,roundId:a=le(),random:o=Math.random}){let s=t.filter(e=>Number(e.chips)>=10).slice(0,5);if(s.length<2)throw Error(`至少需要 2 名有足够筹码的玩家`);let c=A(ue(),o),l={},u=s.map((e,t)=>{let n=c.slice(t*3,t*3+3);return l[e.address]=n,{address:e.address,name:e.name,avatar:y(e.avatar),publicKey:e.publicKey,chips:Number(e.chips)-10,status:`active`,looked:!1,bet:10}}),d,f=S(i),p=f?u.findIndex(e=>S(e.address)===f):-1;return d=p>=0?u[p].address:u[Math.floor(o()*u.length)].address,{roomCode:k(e),roundId:a,status:`playing`,host:S(n),seq:r+1,startedAt:Date.now(),ante:10,pot:10*u.length,currentBet:10,turnAddress:d,players:u,lastAction:`本局开始`,winner:null,showdown:null,appliedEventIds:[],hands:l,actionCounts:{},compareReveal:{}}}function me(e){if(!e)return null;let t={...e};return delete t.hands,K(t)}function he(e,t){let n=L(e);return!n||!t||typeof t!=`object`?null:{...n,hands:K(t)}}function L(e){return!e||typeof e!=`object`||!e.roundId||!Array.isArray(e.players)?null:{roomCode:k(e.roomCode),roundId:String(e.roundId),status:e.status===`finished`?`finished`:`playing`,host:S(e.host),seq:Number(e.seq)||1,startedAt:Number(e.startedAt)||Date.now(),ante:Number(e.ante)||10,pot:Math.max(0,Number(e.pot)||0),currentBet:Math.max(0,Number(e.currentBet)||10),turnAddress:S(e.turnAddress),players:e.players.map(ve).filter(Boolean),lastAction:String(e.lastAction||``),winner:e.winner?S(e.winner):null,showdown:e.showdown&&typeof e.showdown==`object`?K(e.showdown):null,appliedEventIds:Array.isArray(e.appliedEventIds)?e.appliedEventIds.map(String):[],actionCounts:e.actionCounts&&typeof e.actionCounts==`object`?K(e.actionCounts):{},compareReveal:e.compareReveal&&typeof e.compareReveal==`object`?K(e.compareReveal):{},lastCompare:e.lastCompare&&typeof e.lastCompare==`object`?K(e.lastCompare):null,finishedAt:e.finishedAt?Number(e.finishedAt):null}}function R(e,t,n){let r=L(e);if(!r||r.status!==`playing`)return{ok:!1,error:`当前没有进行中的牌局`};let i=S(n),a=r.players.find(e=>e.address===i);if(!a||a.status!==`active`)return{ok:!1,error:`玩家不在本轮牌局中`};if(r.turnAddress!==i)return{ok:!1,error:`还没有轮到你操作`};let o=t?.action;if(![`look`,`call`,`raise`,`compare`,`fold`].includes(o))return{ok:!1,error:`未知操作`};if(o===`look`)return a.looked?{ok:!1,error:`已经看过牌`}:{ok:!0};if(o===`call`)return a.chips>=10?{ok:!0}:{ok:!1,error:`筹码不足,不能跟注`};if(o===`raise`){let e=Number(t.amount);if(!oe.includes(e))return{ok:!1,error:`加注档位无效`};let n=r.currentBet+e-a.bet;return a.chips>=n?{ok:!0}:{ok:!1,error:`筹码不足,不能加注`}}if(o===`compare`){let e=S(t.target),n=r.players.find(t=>t.address===e);if(!e||e===i||!n||n.status!==`active`)return{ok:!1,error:`请选择有效的比牌对象`};if(!r.players.filter(e=>e.status===`active`).every(e=>(r.actionCounts?.[e.address]||0)>=1))return{ok:!1,error:`第一轮不能比牌,请先跟注或加注`};let o=Math.max(0,r.currentBet-a.bet);return a.chips>=o?{ok:!0}:{ok:!1,error:`筹码不足,不能比牌`}}return{ok:!0}}function z(e,t,n){let r=R(e,t,n);if(!r.ok)return{ok:!1,error:r.error,state:e};let i=K(e),a=S(n),o=i.players.find(e=>e.address===a),s=String(t.eventId);if(i.appliedEventIds?.includes(s))return{ok:!0,state:i,duplicate:!0};if(Array.isArray(i.appliedEventIds)||(i.appliedEventIds=[]),i.appliedEventIds.push(s),t.action===`look`)return o.looked=!0,i.lastAction=`${o.name} 看牌`,G(i);if(t.action===`fold`)return o.status=`folded`,i.lastAction=`${o.name} 弃牌`,H(i,a),U(i,a);if(t.action===`call`)return o.chips-=10,o.bet+=10,i.pot+=10,i.lastAction=`${o.name} 跟注`,H(i,a),U(i,a);if(t.action===`raise`){let e=Number(t.amount);return i.currentBet+=e,V(i,o),i.lastAction=`${o.name} 加注 ${e}`,H(i,a),U(i,a)}if(t.action===`compare`){V(i,o);let e=S(t.target),n=i.players.find(t=>t.address===e),r=o.looked,s=n.looked,c=F(i.hands?.[a]||[],i.hands?.[e]||[]),l=c>=0?o:n,u=c>=0?n:o;u.status=`folded`,u.looked=!0,i.compareReveal||={};let d=r&&s,f=!r&&!s;if(d)i.compareReveal[a]=i.hands?.[e]||[],i.compareReveal[e]=i.hands?.[a]||[];else if(f)i.compareReveal[u.address]=i.hands?.[l.address]||[];else{let e=r?o:n,t=r?n:o;i.compareReveal[e.address]=i.hands?.[t.address]||[],i.compareReveal[u.address]=i.hands?.[l.address]||[]}return i.lastCompare={initiator:a,target:e,winner:l.address,loser:u.address,initiatorLooked:r,targetLooked:s,timestamp:Date.now()},i.lastAction=`${o.name} 与 ${n.name} 比牌,${u.name} 出局`,H(i,a),U(i,a)}return{ok:!1,error:`未知操作`,state:e}}function B(e){return(e?.players||[]).filter(e=>e.status===`active`)}function ge(e,t){let n=(e?.players||[]).find(e=>e.address===S(t));if(!e||e.status!==`playing`||!n||n.status!==`active`||e.turnAddress!==n.address)return[];let r=[];return n.looked||r.push(`look`),R(e,{action:`call`},n.address).ok&&r.push(`call`),oe.some(t=>R(e,{action:`raise`,amount:t},n.address).ok)&&r.push(`raise`),B(e).length>1&&e.players.filter(e=>e.status===`active`).every(t=>(e.actionCounts?.[t.address]||0)>=1)&&r.push(`compare`),r.push(`fold`),r}function V(e,t){let n=Math.max(0,e.currentBet-t.bet);t.chips-=n,t.bet+=n,e.pot+=n}function H(e,t){e.actionCounts||={},e.actionCounts[t]=(e.actionCounts[t]||0)+1}function U(e,t){let n=B(e);return n.length===1?_e(e,n[0].address):(e.turnAddress=W(e,t),G(e))}function _e(e,t){let n=e.players.find(e=>e.address===t),r=e.pot;return n&&(n.chips+=e.pot),e.status=`finished`,e.winner=t,e.winAmount=r,e.turnAddress=``,e.showdown=e.hands?K(e.hands):null,e.lastAction=n?`${n.name} 赢得 ${r} 筹码`:`本局结束`,e.pot=0,e.finishedAt=Date.now(),G(e)}function W(e,t){let n=e.players,r=Math.max(0,n.findIndex(e=>e.address===S(t)));for(let e=1;e<=n.length;e++){let t=n[(r+e)%n.length];if(t.status===`active`)return t.address}return``}function G(e){return e.seq=Number(e.seq||0)+1,{ok:!0,state:e}}function ve(e){if(!e||typeof e!=`object`)return null;let t=S(e.address);return t?{address:t,name:String(e.name||v(t)).slice(0,50),avatar:y(e.avatar),publicKey:String(e.publicKey||``),chips:Math.max(0,Number(e.chips)||0),status:e.status===`folded`?`folded`:`active`,looked:e.looked===!0,bet:Math.max(0,Number(e.bet)||0)}:null}function K(e){return JSON.parse(JSON.stringify(e))}var q={page:`_page_1yyew_1`,headerActions:`_headerActions_1yyew_12`,entry:`_entry_1yyew_18`,entryBrand:`_entryBrand_1yyew_28`,cardMark:`_cardMark_1yyew_34`,entryPanel:`_entryPanel_1yyew_62`,accountLine:`_accountLine_1yyew_72`,joinForm:`_joinForm_1yyew_89`,status:`_status_1yyew_110`,board:`_board_1yyew_116`,table:`_table_1yyew_123`,tableCenter:`_tableCenter_1yyew_144`,finishBanner:`_finishBanner_1yyew_173`,finishBannerIcon:`_finishBannerIcon_1yyew_192`,seats:`_seats_1yyew_196`,seat:`_seat_1yyew_196`,seatTurn:`_seatTurn_1yyew_218`,seatWinner:`_seatWinner_1yyew_223`,seatFolded:`_seatFolded_1yyew_228`,seatAvatar:`_seatAvatar_1yyew_232`,seatMain:`_seatMain_1yyew_251`,seatMeta:`_seatMeta_1yyew_252`,panel:`_panel_1yyew_283`,panelSection:`_panelSection_1yyew_290`,panelTitle:`_panelTitle_1yyew_300`,hand:`_hand_1yyew_310`,card:`_card_1yyew_34`,red:`_red_1yyew_344`,black:`_black_1yyew_348`,cardBack:`_cardBack_1yyew_352`,handLabel:`_handLabel_1yyew_373`,actionGrid:`_actionGrid_1yyew_380`,raiseRow:`_raiseRow_1yyew_381`,statusGrid:`_statusGrid_1yyew_392`,muted:`_muted_1yyew_410`,compareOverlay:`_compareOverlay_1yyew_417`,compareBox:`_compareBox_1yyew_429`,compareTitle:`_compareTitle_1yyew_443`,comparePlayers:`_comparePlayers_1yyew_449`,compareSide:`_compareSide_1yyew_456`,compareWinner:`_compareWinner_1yyew_468`,compareName:`_compareName_1yyew_473`,compareCards:`_compareCards_1yyew_484`,compareLabel:`_compareLabel_1yyew_509`,compareVs:`_compareVs_1yyew_515`,compareResult:`_compareResult_1yyew_522`},J=n(),ye=`zhajinhua`;function Y(...e){return e.filter(Boolean).join(` `)}function X(e,t){return String(e||``).toLowerCase()===String(t||``).toLowerCase()}function be(e=``){return e?`${e.slice(0,6)}...${e.slice(-4)}`:``}function xe(e){return e.displayName||e.username}function Se(e){let t=String(e||``),n=t.slice(-1);return{rank:t.slice(0,-1),suit:{S:`♠`,H:`♥`,C:`♣`,D:`♦`}[n]||n,color:n===`H`||n===`D`?`red`:`black`}}function Z(){let{t:e}=l(),t=b(e=>e.isDarkMode),n=b(e=>e.setIsDarkMode),r=b(e=>e.addToast),i=s(e=>e.initializeUser),[v,y]=(0,C.useState)(``),[re,x]=(0,C.useState)(!1),S=(0,C.useRef)(``),w=(0,C.useRef)(!1),[T,E]=(0,C.useState)(20),[D,O]=(0,C.useState)(``),[se,k]=(0,C.useState)({}),[ce,le]=(0,C.useState)(!0),[ue,A]=(0,C.useState)(!1),j=(0,C.useRef)(null),M=(0,C.useRef)(new Set),N=te({gameId:ye,onError:e=>r(e,`error`),getPlayerPayload:e=>{let t=o(e.danger);return{address:e.address,name:xe(e),avatar:e.avatar||``,publicKey:t.public_key,joinedAt:Date.now()}}}),P=(0,C.useMemo)(()=>N.userIdentity?.danger?o(N.userIdentity.danger):null,[N.userIdentity?.danger]);(0,C.useEffect)(()=>{i()},[i]),(0,C.useEffect)(()=>{let e=new URLSearchParams(window.location.search).get(`room`)?.toUpperCase();e&&(y(e),S.current=e)},[]),(0,C.useEffect)(()=>{let t=S.current;!t||w.current||!N.isBackendReady||!N.userIdentity||(w.current=!0,S.current=``,N.joinRoom(t).then(t=>{t&&r(e(`game.toast.joinedRoom`),`success`)}))},[N.isBackendReady,N.userIdentity,N.joinRoom,r,e]),(0,C.useEffect)(()=>{if(!N.roomCode)return;let e=`/game/zhajinhua?room=${N.roomCode}`;`${window.location.pathname}${window.location.search}`!==e&&window.history.replaceState(null,``,e)},[N.roomCode]);let F=(0,C.useMemo)(()=>_(N.messages,{gameId:ye,roomCode:N.roomCode}),[N.messages,N.roomCode]),L=(0,C.useMemo)(()=>ee(N.messages,{gameId:ye,roomCode:N.roomCode}),[N.messages,N.roomCode])?.payload?.round||null,R=L?.host||F.hostAddress,V=X(R,N.userIdentity?.address),H=N.userIdentity?.address?.toLowerCase()||``,U=L?.players.find(e=>X(e.address,H)),_e=U||F.players.find(e=>X(e.address,H)),W=se[L?.roundId||``]||null,G=L?.showdown&&H?L.showdown[H]:null,ve=L?.compareReveal&&H?L.compareReveal[H]:null,K=G||ve||(U?.looked?W:null),Z=ge(L,H),we=B(L),Te=V&&fe(F.players.map(e=>({...e,chips:L?.players.find(t=>X(t.address,e.address))?.chips??1e3}))),Ee=N.roomCode&&typeof window<`u`?`${window.location.origin}/game/zhajinhua?room=${N.roomCode}`:``;(0,C.useEffect)(()=>{if(!(!L||!N.userIdentity||!P||!R))for(let e of N.roomEvents){let t=e.event;if(!t||t.event!==`deal:private`)continue;let n=t.payload;if(n.roundId!==L.roundId||!X(n.recipient,N.userIdentity.address))continue;let r=c(n.encrypted||``,{senderPublicKey:n.senderPublicKey||``,recipientPrivateKey:P.private_key});if(r)try{let e=JSON.parse(r);Array.isArray(e)&&k(t=>({...t,[L.roundId]:e.map(String)}))}catch{}}},[L?.roundId,N.roomEvents,N.userIdentity,R,P?.private_key]),(0,C.useEffect)(()=>{if(L?.status===`finished`&&L.winner){le(!0);let e=setTimeout(()=>le(!1),5e3);return()=>clearTimeout(e)}},[L?.finishedAt,L?.status,L?.winner]),(0,C.useEffect)(()=>{if(L?.lastCompare?.timestamp){A(!0);let e=setTimeout(()=>A(!1),5e3);return()=>clearTimeout(e)}else A(!1)},[L?.lastCompare?.timestamp,L?.roundId]),(0,C.useEffect)(()=>{if(!V||!L||L.status!==`playing`)return;let e=j.current;if(!e)return;let t=N.roomEvents.filter(e=>{let t=e.event;if(!t||t.event!==`player:action`)return!1;let n=t.payload.actionEvent;return!(!n||n.roundId!==L.roundId||M.current.has(n.eventId)||L.appliedEventIds?.includes(n.eventId))});if(t.length===0)return;let n=!1;async function r(){let r=he(L,e);if(r)for(let e of t){if(n)return;let t=(e.event?.payload).actionEvent,i=z(r,t,e.message.author);if(!i.ok){let n=e.message.author;if(!X(n,N.userIdentity?.address)&&r.status===`playing`&&X(r.turnAddress,n)){let e=z(r,I({roundId:r.roundId,action:`call`,amount:void 0,target:void 0}),n);if(e.ok)i=e;else{let e=z(r,I({roundId:r.roundId,action:`fold`,amount:void 0,target:void 0}),n);e.ok&&(i=e)}}if(!i.ok){M.current.add(t.eventId);continue}}if(r=i.state,j.current=i.state.hands,await Ae(i.state)&&M.current.add(t.eventId),i.state.status===`finished`)break}}return r(),()=>{n=!0}},[L?.roundId,L?.seq,L?.status,N.roomEvents,V]);async function De(){await N.createRoom()&&r(e(`game.toast.roomCreated`),`success`)}async function Oe(t){t.preventDefault(),await N.joinRoom(v)&&r(e(`game.toast.joinedRoom`),`success`)}async function ke(){Ee&&(await navigator.clipboard.writeText(Ee),x(!0),setTimeout(()=>x(!1),1400))}async function Ae(e){return N.sendRoomEvent(`room:state`,{round:me(e),seq:e.seq})}async function je(){if(!N.userIdentity||!P||!Te){r(e(`game.zhajinhua.error.notEnoughPlayers`),`error`);return}try{let t=F.players.map(e=>({...e,chips:L?.players.find(t=>X(t.address,e.address))?.chips??1e3})),n=pe({roomCode:N.roomCode,players:t,hostAddress:N.userIdentity.address,previousSeq:L?.seq||0,previousWinner:L?.winner||``});j.current=n.hands,M.current.clear(),await Ae(n);for(let e of n.players){let t=a(JSON.stringify(n.hands[e.address]),{senderPrivateKey:P.private_key,recipientPublicKey:e.publicKey});await N.sendRoomEvent(`deal:private`,{roundId:n.roundId,recipient:e.address,senderPublicKey:P.public_key,encrypted:t})}k(e=>({...e,[n.roundId]:n.hands[N.userIdentity.address.toLowerCase()]})),r(e(`game.zhajinhua.toast.roundStarted`),`success`)}catch(t){r(t instanceof Error?t.message:e(`game.zhajinhua.error.startFailed`),`error`)}}async function $(e,t={}){if(!L||!N.userIdentity)return;let n=I({roundId:L.roundId,action:e,amount:t.amount,target:t.target});await N.sendRoomEvent(`player:action`,{actionEvent:n})}let Me=L?.status===`playing`?X(L.turnAddress,H)?e(`game.zhajinhua.status.yourTurn`):L.turnAddress?e(`game.zhajinhua.status.waitingPlayer`,{player:Q(L.turnAddress,F.players)}):e(`game.status.playing`):L?.status===`finished`?e(`game.status.finished`):e(`game.status.waitingStart`),Ne=we.filter(e=>!X(e.address,H));return(0,J.jsx)(p,{sidebar:({closeSidebar:e})=>(0,J.jsx)(g,{activeGame:`zhajinhua`,closeSidebar:e}),headerTitle:(0,J.jsx)(`h2`,{className:`header-title`,children:e(`game.zhajinhua.title`)}),headerRight:(0,J.jsxs)(`div`,{className:q.headerActions,children:[N.roomCode&&(0,J.jsxs)(`button`,{className:`btn btn-sm`,onClick:ke,children:[(0,J.jsx)(f,{size:14}),e(re?`common.copied`:`game.action.shareRoom`)]}),(0,J.jsx)(`button`,{className:`btn btn-icon`,onClick:()=>n(!t),title:e(`common.theme.toggle`),children:t?(0,J.jsx)(u,{size:16}):(0,J.jsx)(d,{size:16})})]}),children:(0,J.jsx)(`main`,{className:q.page,children:N.roomCode?(0,J.jsxs)(`div`,{className:q.board,children:[(0,J.jsxs)(`section`,{className:q.table,children:[L?.status===`finished`&&L.winner&&ce&&(0,J.jsxs)(`div`,{className:q.finishBanner,children:[(0,J.jsx)(`span`,{className:q.finishBannerIcon,children:`🏆`}),(0,J.jsx)(`span`,{children:e(`game.zhajinhua.result.winChips`,{player:Q(L.winner,F.players),amount:L.winAmount??0})})]}),L?.lastCompare&&ue&&(0,J.jsx)(Ce,{lastCompare:L.lastCompare,compareReveal:L.compareReveal||{},myAddress:H,myDealtHand:W,lobbyPlayers:F.players,onClose:()=>A(!1)}),(0,J.jsxs)(`div`,{className:q.tableCenter,children:[(0,J.jsx)(`span`,{children:e(`game.zhajinhua.pot`)}),(0,J.jsx)(`strong`,{children:L?.pot||0}),(0,J.jsx)(`small`,{children:e(`game.zhajinhua.currentBet`,{amount:L?.currentBet||0})})]}),(0,J.jsx)(`div`,{className:q.seats,children:F.players.map(t=>{let n=L?.players.find(e=>X(e.address,t.address)),r=L?.turnAddress===t.address,i=L?.winner===t.address,a=n?.status===`folded`;return(0,J.jsxs)(`div`,{className:Y(q.seat,r&&q.seatTurn,i&&q.seatWinner,a&&q.seatFolded),children:[(0,J.jsx)(`div`,{className:q.seatAvatar,children:(0,J.jsx)(`img`,{src:ie(t.address,t.avatar),alt:``})}),(0,J.jsxs)(`div`,{className:q.seatMain,children:[(0,J.jsx)(`strong`,{translate:`no`,children:t.name}),(0,J.jsx)(`span`,{translate:`no`,children:be(t.address)})]}),(0,J.jsxs)(`div`,{className:q.seatMeta,children:[(0,J.jsx)(`span`,{children:e(`game.zhajinhua.chips`,{amount:n?.chips??1e3})}),(0,J.jsx)(`span`,{children:n?.status===`folded`?e(`game.zhajinhua.player.folded`):n?.looked?e(`game.zhajinhua.player.looked`):e(L?`game.zhajinhua.player.inRound`:`game.status.waitingStart`)})]})]},t.address)})})]}),(0,J.jsxs)(`aside`,{className:q.panel,children:[(0,J.jsxs)(`div`,{className:q.panelSection,children:[(0,J.jsxs)(`div`,{className:q.panelTitle,children:[(0,J.jsx)(h,{size:16}),(0,J.jsx)(`span`,{children:e(`game.zhajinhua.myHand`)})]}),(0,J.jsx)(`div`,{className:q.hand,children:K?K.map(e=>{let t=Se(e),n=`${t.suit}${t.rank}`;return(0,J.jsxs)(`div`,{className:Y(q.card,q[t.color]),children:[(0,J.jsx)(`span`,{children:n}),(0,J.jsx)(`i`,{children:t.suit})]},e)}):[0,1,2].map(e=>(0,J.jsx)(`div`,{className:q.cardBack,children:(0,J.jsx)(h,{size:18})},e))}),(0,J.jsx)(`p`,{className:q.handLabel,children:K?de(K):e(W?`game.zhajinhua.hand.unlooked`:`game.zhajinhua.hand.waitingDeal`)})]}),(0,J.jsxs)(`div`,{className:q.panelSection,children:[(0,J.jsxs)(`div`,{className:q.panelTitle,children:[(0,J.jsx)(m,{size:16}),(0,J.jsx)(`span`,{children:e(`game.actions`)})]}),V?(0,J.jsxs)(`button`,{className:`btn btn-primary btn-full`,onClick:je,disabled:!Te||L?.status===`playing`,children:[(0,J.jsx)(ae,{size:16}),L?.status===`finished`?e(`game.action.nextRound`):e(`game.action.startRound`)]}):(0,J.jsx)(`p`,{className:q.muted,children:e(`game.waitingHost`)}),V&&L?.status===`finished`&&!Te&&(0,J.jsx)(`p`,{className:q.muted,children:e(`game.zhajinhua.error.tooManyLowChips`)}),(0,J.jsxs)(`div`,{className:q.actionGrid,children:[(0,J.jsxs)(`button`,{className:`btn btn-secondary`,disabled:!Z.includes(`look`),onClick:()=>$(`look`),children:[(0,J.jsx)(ne,{size:16}),e(`game.zhajinhua.action.look`)]}),(0,J.jsx)(`button`,{className:`btn btn-secondary`,disabled:!Z.includes(`call`),onClick:()=>$(`call`),children:e(`game.zhajinhua.action.call`)}),(0,J.jsx)(`button`,{className:`btn btn-secondary`,disabled:!Z.includes(`fold`),onClick:()=>$(`fold`),children:e(`game.zhajinhua.action.fold`)})]}),(0,J.jsxs)(`div`,{className:q.raiseRow,children:[(0,J.jsx)(`select`,{className:`input input-compact`,value:T,onChange:e=>E(Number(e.target.value)),children:oe.map(t=>(0,J.jsx)(`option`,{value:t,children:e(`game.zhajinhua.action.raiseOption`,{amount:t})},t))}),(0,J.jsx)(`button`,{className:`btn btn-secondary`,disabled:!Z.includes(`raise`),onClick:()=>$(`raise`,{amount:T}),children:e(`game.zhajinhua.action.raise`)})]}),(0,J.jsxs)(`div`,{className:q.raiseRow,children:[(0,J.jsxs)(`select`,{className:`input input-compact`,value:D,onChange:e=>O(e.target.value),children:[(0,J.jsx)(`option`,{value:``,children:e(`game.zhajinhua.action.comparePlaceholder`)}),Ne.map(e=>(0,J.jsx)(`option`,{value:e.address,children:e.name},e.address))]}),(0,J.jsx)(`button`,{className:`btn btn-secondary`,disabled:!Z.includes(`compare`)||!D,onClick:()=>$(`compare`,{target:D}),children:e(`game.zhajinhua.action.compare`)})]})]}),(0,J.jsxs)(`div`,{className:q.panelSection,children:[(0,J.jsx)(`div`,{className:q.panelTitle,children:e(`game.status.title`)}),(0,J.jsxs)(`div`,{className:q.statusGrid,children:[(0,J.jsx)(`span`,{children:e(`game.host`)}),(0,J.jsx)(`strong`,{children:R?be(R):`-`}),(0,J.jsx)(`span`,{children:e(`game.zhajinhua.myChips`)}),(0,J.jsx)(`strong`,{children:_e?.chips??1e3}),(0,J.jsx)(`span`,{children:e(`game.round`)}),(0,J.jsx)(`strong`,{children:Me}),(0,J.jsx)(`span`,{children:e(`game.hint`)}),(0,J.jsx)(`strong`,{children:L?.lastAction||e(`game.waitingPlayers`)})]})]})]})]}):(0,J.jsxs)(`section`,{className:q.entry,children:[(0,J.jsxs)(`div`,{className:q.entryBrand,children:[(0,J.jsx)(`div`,{className:q.cardMark,children:(0,J.jsx)(h,{size:48})}),(0,J.jsxs)(`div`,{children:[(0,J.jsx)(`h1`,{children:e(`game.zhajinhua.hero.title`)}),(0,J.jsx)(`p`,{children:e(`game.hero.desc`)})]})]}),(0,J.jsxs)(`div`,{className:q.entryPanel,children:[(0,J.jsxs)(`div`,{className:q.accountLine,children:[(0,J.jsx)(`span`,{children:e(`game.currentAccount`)}),(0,J.jsx)(`strong`,{translate:`no`,children:N.userIdentity?.displayName||e(`web3.notSignedIn`)})]}),(0,J.jsxs)(`button`,{className:`btn btn-primary`,disabled:N.joining,onClick:De,children:[(0,J.jsx)(m,{size:16}),e(`game.action.createRoom`)]}),(0,J.jsxs)(`form`,{onSubmit:Oe,className:q.joinForm,children:[(0,J.jsxs)(`label`,{children:[e(`game.roomCode`),(0,J.jsx)(`input`,{value:v,maxLength:8,onChange:e=>y(e.target.value.toUpperCase()),placeholder:e(`game.roomCode.placeholder`)})]}),(0,J.jsx)(`button`,{className:`btn`,disabled:N.joining||!v,children:e(`game.action.joinRoom`)})]}),(0,J.jsx)(`p`,{className:q.status,children:N.isBackendReady?e(`game.status.nodeConnected`):e(`game.status.nodeConnecting`)})]})]})})})}function Ce({lastCompare:e,compareReveal:t,myAddress:n,myDealtHand:r,lobbyPlayers:i,onClose:a}){let{t:o}=l(),s=Q(e.initiator,i),c=Q(e.target,i),u=Q(e.winner,i),d=X(n,e.initiator),f=X(n,e.target),p=d||f;function m(e,i){return t[e]||(p&&X(e,n)&&i&&r?r:null)}let g=m(e.initiator,e.initiatorLooked),_=m(e.target,e.targetLooked);return(0,J.jsx)(`div`,{className:q.compareOverlay,onClick:a,children:(0,J.jsxs)(`div`,{className:q.compareBox,onClick:e=>e.stopPropagation(),children:[(0,J.jsx)(`div`,{className:q.compareTitle,children:o(`game.zhajinhua.compareResult`)}),(0,J.jsxs)(`div`,{className:q.comparePlayers,children:[(0,J.jsxs)(`div`,{className:Y(q.compareSide,X(e.winner,e.initiator)&&q.compareWinner),children:[(0,J.jsx)(`div`,{className:q.compareName,children:s}),(0,J.jsx)(`div`,{className:q.compareCards,children:g?g.map(e=>{let t=Se(e);return(0,J.jsxs)(`div`,{className:Y(q.card,q[t.color]),children:[(0,J.jsx)(`span`,{children:t.rank}),(0,J.jsx)(`i`,{children:t.suit})]},e)}):[0,1,2].map(e=>(0,J.jsx)(`div`,{className:q.cardBack,children:(0,J.jsx)(h,{size:18})},e))}),g&&(0,J.jsx)(`div`,{className:q.compareLabel,children:de(g)})]}),(0,J.jsx)(`div`,{className:q.compareVs,children:`VS`}),(0,J.jsxs)(`div`,{className:Y(q.compareSide,X(e.winner,e.target)&&q.compareWinner),children:[(0,J.jsx)(`div`,{className:q.compareName,children:c}),(0,J.jsx)(`div`,{className:q.compareCards,children:_?_.map(e=>{let t=Se(e);return(0,J.jsxs)(`div`,{className:Y(q.card,q[t.color]),children:[(0,J.jsx)(`span`,{children:t.rank}),(0,J.jsx)(`i`,{children:t.suit})]},e)}):[0,1,2].map(e=>(0,J.jsx)(`div`,{className:q.cardBack,children:(0,J.jsx)(h,{size:18})},e))}),_&&(0,J.jsx)(`div`,{className:q.compareLabel,children:de(_)})]})]}),(0,J.jsx)(`div`,{className:q.compareResult,children:o(`game.zhajinhua.result.winner`,{player:u})}),(0,J.jsx)(`button`,{className:`btn btn-sm`,onClick:a,children:o(`common.close`)})]})})}function Q(e,t){let n=e.toLowerCase();return t.find(e=>e.address===n)?.name||be(e)}var we=i(`/game/zhajinhua/`)({component:Z});export{we as Route};
|