WebRTC sinyal fix + reaktif eşler kolonu + anlık disconnect
sdk/Peer.js — :rtcpack: her zaman WebSocket üzerinden:
- RTC bağlandıktan sonra renegotiasyon (yeni stream ekleme) sırasında
ICE adayları DataChannel'a yönlendiriliyordu → bağlantı kurulamıyordu
- forceWS = pack.type === ':rtcpack:' → yönlendirme mantığını atlar,
her koşulda WebSocket kullanır
- Signalingi writable flag da engellemez (rtcpack her zaman geçer)
sdk/index.js — peer/disconnect tam işleme:
- pairs.delete(id) eklendi (kopan eş pairs'ten çıkar)
- me.emit('peer/disconnect', peer) eklendi (Studio dinleyebilsin)
public/studio/Studio.js — reaktif eşler kolonu:
- _peersCol referansı: _pushPeersColumn'da saklanır
- _rebuildPeerItems(): mwse.pairs'i okuyup Column.setItems() çağırır
→ kolon her zaman anında güncellenir (tıklama gerekmez)
- Olaylar: accepted/pair + end/pair + peer/disconnect → _rebuildPeerItems()
- Kabul eden taraf: _pushPeersColumn yoksa aç, varsa rebuild
- Disconnect status bar'da kırmızı hata mesajı
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f5565f5df0
commit
d468c95adf
|
|
@ -7,16 +7,17 @@ export default class Studio {
|
|||
this.mwse = mwse;
|
||||
this._el = typeof container === 'string'
|
||||
? document.querySelector(container) : container;
|
||||
this._view = null; // mount() içinde oluşturulur
|
||||
this._view = null;
|
||||
this._devices = { cameras: [], microphones: [] };
|
||||
this._statusEl = null;
|
||||
this._notifArea = null;
|
||||
this._myIPEl = null;
|
||||
this._myUUIDEl = null;
|
||||
this._localGrid = null; // "Gönderiyorum" tile grid'i
|
||||
this._remoteGrid = null; // "Geliyor" tile grid'i
|
||||
this._localGrid = null;
|
||||
this._remoteGrid = null;
|
||||
this._streamsPanel = null;
|
||||
this._styleOK = false;
|
||||
this._peersCol = null; // reaktif güncelleme için referans
|
||||
}
|
||||
|
||||
async mount() {
|
||||
|
|
@ -51,18 +52,29 @@ export default class Studio {
|
|||
// Gelen eşleme isteği → bildirim banner
|
||||
this.mwse.me.on('request/pair', peer => this._showPairRequest(peer));
|
||||
|
||||
// Eşleşme onaylandı
|
||||
// Eşleşme onaylandı (istek gönderen taraf)
|
||||
this.mwse.me.on('accepted/pair', peer => {
|
||||
this._watchIncoming(peer);
|
||||
if (this.mwse.virtualPressure.APIPAddress) {
|
||||
this.mwse.me.info.set('ip', this.mwse.virtualPressure.APIPAddress);
|
||||
}
|
||||
this._view.refresh();
|
||||
this._rebuildPeerItems();
|
||||
this._setStatus('online', `${this._peerLabel(peer)} eşleşmesi kuruldu`);
|
||||
});
|
||||
|
||||
this.mwse.me.on('end/pair', () => this._view.refresh());
|
||||
this.mwse.on('room', () => this._view.refresh());
|
||||
// Eşleşme bitti (karşı taraf koptu veya endPair çağrıldı)
|
||||
this.mwse.me.on('end/pair', (from) => {
|
||||
this._rebuildPeerItems();
|
||||
this._setStatus('', `${typeof from === 'string' ? from.slice(-8) : ''} ayrıldı`);
|
||||
});
|
||||
|
||||
// WebSocket bağlantısı kopunca (sayfa yenileme, ağ kesilmesi)
|
||||
this.mwse.me.on('peer/disconnect', peer => {
|
||||
this._rebuildPeerItems();
|
||||
this._setStatus('error', `${this._peerLabel(peer)} bağlantısı kesildi`);
|
||||
});
|
||||
|
||||
this.mwse.on('room', () => this._view.refresh());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
|
@ -347,8 +359,10 @@ export default class Studio {
|
|||
if (this.mwse.virtualPressure.APIPAddress) {
|
||||
this.mwse.me.info.set('ip', this.mwse.virtualPressure.APIPAddress);
|
||||
}
|
||||
this._view.popTo(1);
|
||||
this._pushPeersColumn();
|
||||
// Eşler kolonunu aç ve anında doldur
|
||||
if (this._view.depth < 2) this._view.popTo(1);
|
||||
if (!this._peersCol) this._pushPeersColumn();
|
||||
else this._rebuildPeerItems();
|
||||
this._setStatus('online', `${this._peerLabel(peer)} kabul edildi`);
|
||||
} else {
|
||||
this._setStatus('error', 'Eşleşme kurulamadı');
|
||||
|
|
@ -406,7 +420,8 @@ export default class Studio {
|
|||
});
|
||||
}
|
||||
|
||||
const col = this._view.pushColumn('Eşler', items);
|
||||
this._peersCol = this._view.pushColumn('Eşler', items);
|
||||
const col = this._peersCol;
|
||||
col.addAction('<span class="mi mi-sm">person_search</span> ID ile ara', '', () => {
|
||||
this._showModal({
|
||||
title: 'Eşe bağlan',
|
||||
|
|
@ -428,6 +443,26 @@ export default class Studio {
|
|||
return peer.info?.info?.ip || peer.socketId.slice(-8);
|
||||
}
|
||||
|
||||
// Eşler kolonunu mevcut mwse.pairs verisiyle canlı olarak günceller.
|
||||
// Kolon henüz açılmamışsa no-op.
|
||||
_rebuildPeerItems() {
|
||||
if (!this._peersCol) return;
|
||||
const pairs = [...this.mwse.pairs.values()];
|
||||
const items = pairs.map(peer => ({
|
||||
icon: peer.rtc?.active ? 'sensors' : 'person',
|
||||
label: this._peerLabel(peer),
|
||||
meta: () => this._peerMeta(peer),
|
||||
onSelect: () => { this._view.popTo(2); this._pushPeerColumn(peer); }
|
||||
}));
|
||||
if (!items.length) {
|
||||
items.push({
|
||||
icon: 'person_off', label: 'Henüz eş yok',
|
||||
meta: '"ID ile ara" butonunu kullan', hasChildren: false
|
||||
});
|
||||
}
|
||||
this._peersCol.setItems(items);
|
||||
}
|
||||
|
||||
_peerMeta(peer) {
|
||||
const streams = peer.rtc?._streams?.list() ?? [];
|
||||
const id = peer.socketId.slice(-8);
|
||||
|
|
|
|||
30
sdk/Peer.js
30
sdk/Peer.js
|
|
@ -164,31 +164,31 @@ export default class Peer extends MWSEEventTarget {
|
|||
}
|
||||
|
||||
async send(pack) {
|
||||
const p2pOpen = this.peerConnection && this.rtc?.active;
|
||||
const serverOpen = this.mwse.server.connected;
|
||||
|
||||
let channel;
|
||||
if (p2pOpen && serverOpen) {
|
||||
channel = this.primaryChannel === 'websocket' ? 'websocket' : 'datachannel';
|
||||
} else if (serverOpen) {
|
||||
channel = 'websocket';
|
||||
} else {
|
||||
channel = 'datachannel';
|
||||
}
|
||||
// WebRTC signaling (:rtcpack:) MUST always travel via the WebSocket relay —
|
||||
// never over the DataChannel. This is true even after the p2p connection is
|
||||
// established (renegotiation, new-stream ICE candidates, etc.). The DataChannel
|
||||
// does not exist yet when the first offer/answer exchange happens, and may not
|
||||
// be the right path for out-of-band signaling later either.
|
||||
const forceWS = pack.type === ':rtcpack:';
|
||||
|
||||
const p2pOpen = !forceWS && this.peerConnection && this.rtc?.active;
|
||||
|
||||
const channel = (p2pOpen && serverOpen)
|
||||
? (this.primaryChannel === 'websocket' ? 'websocket' : 'datachannel')
|
||||
: 'websocket';
|
||||
|
||||
if (channel === 'websocket') {
|
||||
if (!this.mwse.writable) {
|
||||
if (!serverOpen) return;
|
||||
if (!this.mwse.writable && !forceWS) {
|
||||
console.warn('MWSE: socket is not writable');
|
||||
return;
|
||||
}
|
||||
// WOM — no waiter registered; the engine returns nil for pack/to (#33).
|
||||
this.mwse.EventPooling.only({ type: 'pack/to', pack, to: this.socketId });
|
||||
} else {
|
||||
if (pack.type !== ':rtcpack:') {
|
||||
this.rtc?.sendMessage(pack);
|
||||
} else {
|
||||
console.warn('MWSE: cannot send :rtcpack: over data channel');
|
||||
}
|
||||
this.rtc?.sendMessage(pack);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -234,7 +234,11 @@ export default class MWSE {
|
|||
});
|
||||
|
||||
ep.signal('peer/disconnect', ({ id }) => {
|
||||
this.peer(id, true).emit('disconnect');
|
||||
const peer = this.peer(id, true);
|
||||
this.pairs.delete(id);
|
||||
peer.emit('disconnect');
|
||||
// me üzerinden de yay, Studio dinleyebilsin.
|
||||
this.peer('me').emit('peer/disconnect', peer);
|
||||
});
|
||||
|
||||
ep.signal('accepted/pair', ({ from, info }) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue