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.mwse = mwse;
|
||||||
this._el = typeof container === 'string'
|
this._el = typeof container === 'string'
|
||||||
? document.querySelector(container) : container;
|
? document.querySelector(container) : container;
|
||||||
this._view = null; // mount() içinde oluşturulur
|
this._view = null;
|
||||||
this._devices = { cameras: [], microphones: [] };
|
this._devices = { cameras: [], microphones: [] };
|
||||||
this._statusEl = null;
|
this._statusEl = null;
|
||||||
this._notifArea = null;
|
this._notifArea = null;
|
||||||
this._myIPEl = null;
|
this._myIPEl = null;
|
||||||
this._myUUIDEl = null;
|
this._myUUIDEl = null;
|
||||||
this._localGrid = null; // "Gönderiyorum" tile grid'i
|
this._localGrid = null;
|
||||||
this._remoteGrid = null; // "Geliyor" tile grid'i
|
this._remoteGrid = null;
|
||||||
this._streamsPanel = null;
|
this._streamsPanel = null;
|
||||||
this._styleOK = false;
|
this._styleOK = false;
|
||||||
|
this._peersCol = null; // reaktif güncelleme için referans
|
||||||
}
|
}
|
||||||
|
|
||||||
async mount() {
|
async mount() {
|
||||||
|
|
@ -51,17 +52,28 @@ export default class Studio {
|
||||||
// Gelen eşleme isteği → bildirim banner
|
// Gelen eşleme isteği → bildirim banner
|
||||||
this.mwse.me.on('request/pair', peer => this._showPairRequest(peer));
|
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.mwse.me.on('accepted/pair', peer => {
|
||||||
this._watchIncoming(peer);
|
this._watchIncoming(peer);
|
||||||
if (this.mwse.virtualPressure.APIPAddress) {
|
if (this.mwse.virtualPressure.APIPAddress) {
|
||||||
this.mwse.me.info.set('ip', 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._setStatus('online', `${this._peerLabel(peer)} eşleşmesi kuruldu`);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.mwse.me.on('end/pair', () => 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());
|
this.mwse.on('room', () => this._view.refresh());
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|
@ -347,8 +359,10 @@ export default class Studio {
|
||||||
if (this.mwse.virtualPressure.APIPAddress) {
|
if (this.mwse.virtualPressure.APIPAddress) {
|
||||||
this.mwse.me.info.set('ip', this.mwse.virtualPressure.APIPAddress);
|
this.mwse.me.info.set('ip', this.mwse.virtualPressure.APIPAddress);
|
||||||
}
|
}
|
||||||
this._view.popTo(1);
|
// Eşler kolonunu aç ve anında doldur
|
||||||
this._pushPeersColumn();
|
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`);
|
this._setStatus('online', `${this._peerLabel(peer)} kabul edildi`);
|
||||||
} else {
|
} else {
|
||||||
this._setStatus('error', 'Eşleşme kurulamadı');
|
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', '', () => {
|
col.addAction('<span class="mi mi-sm">person_search</span> ID ile ara', '', () => {
|
||||||
this._showModal({
|
this._showModal({
|
||||||
title: 'Eşe bağlan',
|
title: 'Eşe bağlan',
|
||||||
|
|
@ -428,6 +443,26 @@ export default class Studio {
|
||||||
return peer.info?.info?.ip || peer.socketId.slice(-8);
|
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) {
|
_peerMeta(peer) {
|
||||||
const streams = peer.rtc?._streams?.list() ?? [];
|
const streams = peer.rtc?._streams?.list() ?? [];
|
||||||
const id = peer.socketId.slice(-8);
|
const id = peer.socketId.slice(-8);
|
||||||
|
|
|
||||||
28
sdk/Peer.js
28
sdk/Peer.js
|
|
@ -164,31 +164,31 @@ export default class Peer extends MWSEEventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
async send(pack) {
|
async send(pack) {
|
||||||
const p2pOpen = this.peerConnection && this.rtc?.active;
|
|
||||||
const serverOpen = this.mwse.server.connected;
|
const serverOpen = this.mwse.server.connected;
|
||||||
|
|
||||||
let channel;
|
// WebRTC signaling (:rtcpack:) MUST always travel via the WebSocket relay —
|
||||||
if (p2pOpen && serverOpen) {
|
// never over the DataChannel. This is true even after the p2p connection is
|
||||||
channel = this.primaryChannel === 'websocket' ? 'websocket' : 'datachannel';
|
// established (renegotiation, new-stream ICE candidates, etc.). The DataChannel
|
||||||
} else if (serverOpen) {
|
// does not exist yet when the first offer/answer exchange happens, and may not
|
||||||
channel = 'websocket';
|
// be the right path for out-of-band signaling later either.
|
||||||
} else {
|
const forceWS = pack.type === ':rtcpack:';
|
||||||
channel = 'datachannel';
|
|
||||||
}
|
const p2pOpen = !forceWS && this.peerConnection && this.rtc?.active;
|
||||||
|
|
||||||
|
const channel = (p2pOpen && serverOpen)
|
||||||
|
? (this.primaryChannel === 'websocket' ? 'websocket' : 'datachannel')
|
||||||
|
: 'websocket';
|
||||||
|
|
||||||
if (channel === 'websocket') {
|
if (channel === 'websocket') {
|
||||||
if (!this.mwse.writable) {
|
if (!serverOpen) return;
|
||||||
|
if (!this.mwse.writable && !forceWS) {
|
||||||
console.warn('MWSE: socket is not writable');
|
console.warn('MWSE: socket is not writable');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// WOM — no waiter registered; the engine returns nil for pack/to (#33).
|
// WOM — no waiter registered; the engine returns nil for pack/to (#33).
|
||||||
this.mwse.EventPooling.only({ type: 'pack/to', pack, to: this.socketId });
|
this.mwse.EventPooling.only({ type: 'pack/to', pack, to: this.socketId });
|
||||||
} else {
|
} else {
|
||||||
if (pack.type !== ':rtcpack:') {
|
|
||||||
this.rtc?.sendMessage(pack);
|
this.rtc?.sendMessage(pack);
|
||||||
} else {
|
|
||||||
console.warn('MWSE: cannot send :rtcpack: over data channel');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -234,7 +234,11 @@ export default class MWSE {
|
||||||
});
|
});
|
||||||
|
|
||||||
ep.signal('peer/disconnect', ({ id }) => {
|
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 }) => {
|
ep.signal('accepted/pair', ({ from, info }) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue