Skip to content

Commit

Permalink
feat: Support showing candidate type
Browse files Browse the repository at this point in the history
  • Loading branch information
ci010 committed Dec 22, 2022
1 parent 3bee723 commit 4494e9b
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 37 deletions.
2 changes: 1 addition & 1 deletion xmcl
Submodule xmcl updated 1 files
+9 −0 packages/client/lan.ts
6 changes: 2 additions & 4 deletions xmcl-keystone-ui/src/views/Multiplayer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<v-btn
:disabled="!state.group"
text
@click="onCopy(joinGroupUrl)"
@click="onCopy(groupId)"
>
<v-icon
v-if="!copied"
Expand All @@ -51,7 +51,7 @@
>
check
</v-icon>
{{ copied ? t('multiplayer.copied') : t('multiplayer.inviteLink') }}
{{ copied ? t('multiplayer.copied') : t('multiplayer.copy') }}
</v-btn>

<div class="text-gray-400 text-sm lg:block hidden">
Expand Down Expand Up @@ -464,10 +464,8 @@ const tNatType = computed(() => ({
}))
const groupId = ref(state.group)
const modified = computed(() => groupId.value !== state.group)
const deleting = ref('')
const deletingName = computed(() => state.connections.find(c => c.id === deleting.value)?.userInfo.name)
const joinGroupUrl = computed(() => `https://xmcl.app/peer?group=${state.group}&inviter=${gameProfile.value.name}`)
const copied = ref(false)
const joiningGroup = useBusy('joinGroup')
Expand Down
25 changes: 25 additions & 0 deletions xmcl-runtime-api/src/services/PeerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ export type ConnectionState = 'closed' | 'connected' | 'connecting' | 'disconnec
export type IceGatheringState = 'complete' | 'gathering' | 'new'
export type SignalingState = 'closed' | 'have-local-offer' | 'have-local-pranswer' | 'have-remote-offer' | 'have-remote-pranswer' | 'stable'

export interface SelectedCandidateInfo {
address: string
port: number
type: 'host' | 'prflx' | 'srflx' | 'relay'
transportType: 'udp' | 'tcp'
}

export interface ConnectionUserInfo extends GameProfileAndTexture {
/**
* The readable text
Expand All @@ -26,6 +33,10 @@ export interface PeerConnection {
id: string
userInfo: ConnectionUserInfo
initiator: boolean
selectedCandidate?: {
local: SelectedCandidateInfo
remote: SelectedCandidateInfo
}

localDescriptionSDP: string
ping: number
Expand Down Expand Up @@ -87,6 +98,20 @@ export class PeerState {
}
}

connectionSelectedCandidate({ id, local, remote }: {
id: string
local: SelectedCandidateInfo
remote: SelectedCandidateInfo
}) {
const conn = this.connections.find(c => c.id === id)
if (conn) {
conn.selectedCandidate = {
local,
remote,
}
}
}

connectionPing(update: { id: string; ping: number }) {
const conn = this.connections.find(c => c.id === update.id)
if (conn) {
Expand Down
4 changes: 3 additions & 1 deletion xmcl-runtime/lib/entities/peer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ export class PeerGroup extends EventEmitter {
.map((b) => ('00' + b.toString(16)).slice(-2))
.join('')
.replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, '$1-$2-$3-$4-$5')
this.emit('heartbeat', id)
if (id !== this.id) {
this.emit('heartbeat', id)
}
return
}
try {
Expand Down
2 changes: 2 additions & 0 deletions xmcl-runtime/lib/entities/peer/PeerHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export interface PeerHost {
getSharedInstance(): InstanceManifest | undefined
getShadedInstancePath(): string
getSharedImagePath(image: string): string
getSharedLibrariesPath(): string
getSharedAssetsPath(): string

onIdentity(id: string, info: ConnectionUserInfo): void
onInstanceShared(id: string, manifest: InstanceManifest): void
Expand Down
57 changes: 49 additions & 8 deletions xmcl-runtime/lib/entities/peer/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export class PeerSession {
private remoteId = ''
#isClosed = false

public lastGameChannelId = undefined as undefined | number

constructor(
/**
* The session id
Expand Down Expand Up @@ -86,23 +88,48 @@ export class PeerSession {
const port = Number.parseInt(label)!
this.logger.log(`Receive minecraft game connection: ${port}`)
const socket = createConnection(port)
socket.on('data', (buf) => channel.sendMessageBinary(buf))
channel.onMessage((data) => socket.write(Buffer.from(data)))
const id = channel.getId()
let buffers: Buffer[] = []
let opened = false
socket.on('data', (buf) => {
if (opened) {
channel.sendMessageBinary(buf)
} else {
buffers.push(buf)
}
})
channel.onMessage((data) => {
socket.write(data)
})
socket.on('close', () => {
this.logger.log(`Socket ${label} closed and close game channel ${channel.getId()}`)
channel.close()
this.logger.log(`Socket ${label} closed and close game channel ${id}`)
if (channel.isOpen()) {
channel.close()
}
})
channel.onClosed(() => {
this.logger.log(`Game connection ${channel.getId()} closed and destroy socket ${label}`)
this.logger.log(`Game connection ${id} closed and destroy socket ${label}`)
socket.destroy()
channel.close()
})
channel.onOpen(() => {
this.logger.log(`Game data channel ${port}(${id}) is opened!`)
for (const buf of buffers) {
channel.sendMessageBinary(buf)
}
buffers = []
opened = true
})
channel.onError((e) => {
this.logger.error(`Game data channel ${port}(${id}) error: %o`, e)
})
this.logger.log(`Create game channel to ${port}`)
this.logger.log(`Create game channel to ${port}(${id})`)
} else if (protocol === 'metadata') {
// this is a metadata channel
this.setChannel(channel)
this.logger.log('Metadata channel created')
} else if (protocol === 'download') {
this.logger.log(`Receive peer download request: ${label}`)
this.logger.log(`Receive peer download request: ${label}(${channel.getId()})`)
const path = unescape(label)
const createStream = (filePath: string) => {
if (filePath.startsWith('/sharing')) {
Expand All @@ -128,6 +155,12 @@ export class PeerSession {
filePath = filePath.substring(1)
}
return createReadStream(host.getSharedImagePath(filePath))
} else if (filePath.startsWith('/assets')) {
filePath = filePath.substring('/assets'.length)
return createReadStream(join(host.getSharedAssetsPath(), filePath))
} else if (filePath.startsWith('/libraries')) {
filePath = filePath.substring('/libraries'.length)
return createReadStream(join(host.getSharedLibrariesPath(), filePath))
}
return 'NOT_FOUND'
}
Expand Down Expand Up @@ -172,7 +205,10 @@ export class PeerSession {
initiate() {
// host
this.logger.log('peer initialize')
this.setChannel(this.connection.createDataChannel(this.id, { ordered: true, protocol: 'metadata' }))
this.setChannel(this.connection.createDataChannel(this.id, {
ordered: true,
protocol: 'metadata',
}))
}

createReadStream(file: string) {
Expand Down Expand Up @@ -218,10 +254,15 @@ export class PeerSession {
}
})
channel.onOpen(() => {
this.logger.log(`Create metadata channel on ${channel.getId()}`)
const info = this.host.getUserInfo()
this.send(MessageIdentity, { ...info })
})
channel.onError((e) => {
this.logger.error('Fail to create metadata channel: %o', e)
})
channel.onClosed(() => {
this.logger.log(`Metadata channel closed: ${channel.getId()}`)
this.close()
})
this.channel = channel
Expand Down
71 changes: 63 additions & 8 deletions xmcl-runtime/lib/entities/peer/messages/lan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@ import { createServer } from 'net'
import { defineMessage, MessageType } from './message'
import { ServerProxy } from '../ServerProxy'
import { listen } from '../../../util/server'
import { DataChannelInitConfig } from 'node-datachannel'

export const MessageLan: MessageType<LanServerInfo> = 'lan'

export const MessageLanEntry = defineMessage(MessageLan, async function (info) {
const pair = this.connection.getSelectedCandidatePair()
if (pair && pair.remote.type === 'host') {
// 'host' means we are in same local network or public network
return
}
// lan message from other peer
let proxy = this.proxies.find(p => p.originalPort === info.port)
let proxy = this.proxies.find(p =>
// this port proxy is already created
p.originalPort === info.port)
if (proxy) {
// Re-broadcast message
this.host.onLanMessage(this.id, { motd: info.motd, port: await proxy.actualPort })
Expand All @@ -20,16 +28,63 @@ export const MessageLanEntry = defineMessage(MessageLan, async function (info) {
const server = createServer((socket) => {
// create game data channel to pipe message
this.logger.log(`Create datachannel to actual port ${info.port}`)
const gameChannel = this.connection.createDataChannel(`${info.port}`, {
const init: DataChannelInitConfig = {
protocol: 'minecraft', // protocol minecraft
ordered: true,
})
this.logger.log(`Data channel: ${gameChannel.getId()}`)
}
if (this.lastGameChannelId) {
init.id = this.lastGameChannelId += 2
}
const gameChannel = this.connection.createDataChannel(`${info.port}`, init)

if (!this.lastGameChannelId) {
this.lastGameChannelId = gameChannel.getId()
}
// the data send before channel connected will be buffered
socket.on('data', (buf) => gameChannel.sendMessageBinary(buf))
gameChannel.onMessage((data) => socket.write(Buffer.from(data)))
socket.on('close', () => gameChannel.close())
gameChannel.onClosed(() => socket.destroy())
let buffers: Buffer[] = []
let opened = false
socket.on('data', (buf) => {
if (!opened) {
buffers.push(buf)
} else if (gameChannel.isOpen()) {
if (!gameChannel.sendMessageBinary(buf)) {
gameChannel.close()
}
}
})
gameChannel.onMessage((data) => {
socket.write(data)
})

socket.on('close', () => {
const id = gameChannel.getId()
this.logger.log(`Close game channel due to socket closed ${info.port}(${id})`)
if (gameChannel.isOpen()) {
gameChannel.close()
}
})
gameChannel.onClosed(() => {
const id = gameChannel.getId()
this.logger.log(`Destroy socket due to game channel is closed ${info.port}(${id})`)
socket.destroy()
gameChannel.close()
})
gameChannel.onError((e) => {
const id = gameChannel.getId()
this.logger.log(`Game channel ${info.port}(${id}) error: %o`, e)
})
gameChannel.onOpen(() => {
const id = gameChannel.getId()
this.logger.log(`Game channel ${info.port}(${id}) opened!`)

for (const buf of buffers) {
if (!gameChannel.sendMessageBinary(buf)) {
break
}
}
buffers = []
opened = true
})
})
proxy = new ServerProxy(info.port, listen(server, info.port, (p) => p + 1), server)
this.logger.log(`Create new server proxy: ${info.port}`)
Expand Down

0 comments on commit 4494e9b

Please sign in to comment.