I'm using supabase realtime channels to build a collaborative editor. I'm using slatejs and I'm broadcasting operations on the channel but the broadcasts appear really slow on other clients and also some of the broadcasts are lost.
This is the main code:
const blockUpdateChannel = supabaseClient.channel(
"block-updates" + DOCUMENT_ID
);
// Applying the received broadcasts to current editor
blockUpdateChannel
.on("broadcast", { event: "blockupdate" }, (event: any) => {
Editor.withoutNormalizing(editor as any, () => {
const operations = event.payload.ops;
operations.forEach((operation: any) => {
console.log(operation);
if (operation.source !== userId) {
editor?.apply(operation);
}
});
});
})
.subscribe();
// sending broadcasts
const ops: any = [];
editor?.operations.forEach((operation: any) => {
var shouldAdd = false;
if (!operation.source) {
if (operation.type !== "set_selection") {
shouldAdd = true;
if (operation.type === "set_node") {
if (operation.newProperties.modifiedByUserId !== undefined) {
shouldAdd = false;
}
}
}
}
if (shouldAdd) {
operation.source = userId;
ops.push(operation);
}
});
if (ops.length) {
console.log("Sending ops", ops);
blockUpdateChannel.send({
type: "broadcast",
event: "blockupdate",
payload: { ops },
});
}
You can debug Supabase Realtime by looking at the (new) Realtime logs at:
https://app.supabase.com/project/YOUR_PROJECT/database/realtime-logs
You can also inspect the websocket frames with Chrome Inspector. There may be some odd messages there. Or you might find that it's disconnecting / reconnecting frequently for some reason.
Related
I am getting error code "NotSupportedError: GATT operation not permitted."
I am trying to connect to ESP32 bluetooth. I tested using nRF Connect and messages are getting to the hardware. Next I tried to use javascript and web-bluetooth. Unfortunately I am getting error on console.error('Argh! ' + error) line of code. The error is happening on characteristic.writeValue()
The code was run on https.
The code below
$(document).ready(function(){
let bluetoothDevice = null;
let requestDeviceParams = {
filters: [
{name: ["konko"]}
],
optionalServices: ['10001001-0000-1000-8000-00805f9b34fb']
}
let name = document.querySelector('#date-input')
$("#synch-date-time").click(() => {
if (document.querySelector('#date-input').value === '') {
console.error('empty date field, please fill the fields')
return
}
asyncResultNotif();
})
async function asyncResultNotif(){
return await navigator.bluetooth.requestDevice(requestDeviceParams)
.then(device => {
bluetoothDevice = device.gatt;
return device.gatt.connect();
})
.then(server => {
if (bluetoothDevice.connected) {
return server.getPrimaryService('10001001-0000-1000-8000-00805f9b34fb');
} else {
console.log('cant connect to prime server')
}
})
.then(service => {
if (bluetoothDevice.connected) {
return service.getCharacteristic('10001111-0000-1000-8000-00805f9b34fb'); // write one value
} else {
console.log('cant connect to characteristic')
}
})
.then(characteristic => {
if (bluetoothDevice.connected) {
// const resetEnergyExpended = Uint8Array.of(1);
// return characteristic.writeValue(resetEnergyExpended);
let data = '{"ssid": "' +name.value
data +='"}'
console.log(data)
let encoder = new TextEncoder('utf-8');
let val = encoder.encode(data);
// return characteristic.writeValue(val.buffer)
return characteristic.writeValue(new Uint8Array([1]))
} else {
console.log('cant send message over BLE')
}
}). then(() => {
if (bluetoothDevice.connected) {
bluetoothDevice.disconnect();
} else {
console.log('> Bluetooth Device is already disconnected');
}
}).catch(error => {
console.error('Argh! ' + error)
});
}
It could be that the Bluetooth GATT characteristic you're writing to is not writable. Can you share your ESP32 server code as well?
It should look something like below. Note that I use BLECharacteristic::PROPERTY_WRITE.
class MyCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
std::string value = pCharacteristic->getValue();
BLECharacteristic *pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID,
BLECharacteristic::PROPERTY_READ |
BLECharacteristic::PROPERTY_WRITE
);
pCharacteristic->setCallbacks(new MyCallbacks());
pCharacteristic->setValue("Hello World");
I found the answer. The problem was with UUID of characteristic. On ESP32 it was "10001111-0000-1000-8000-00805f9b34fb" and when I changed that to "10000000-0000-1000-8000-00805f9b34fb" it starts working. I somewhere read that it may be something with 128bits in UUID. We can mark with as answered.
I am using discordjs/voice in order to sum up for how long users have talked in the voice channel. So far it never logged an data and I noticed that it is because the client.on('speaking' event is not triggered. I have tried looking for solutions to why that happens but couldn`t find any.
Here is the related piece from my code:
if (msg.content === '!scw start') {
if (!msg.guild) {
msg.channel.send('You\'re currently not in a guild!');
return;
};
msg.channel.send('Please wait a moment while I connect to the VC.')
.catch(error => {
console.log(error);
});
const channel = msg.member.voice.channel
if (channel === undefined) {
msg.channel.send('It seems like you\'re not in a VC.');
return
};
const connection = joinVoiceChannel({
channelId: channel.id,
guildId: channel.guild.id,
adapterCreator: channel.guild.voiceAdapterCreator,
selfDeaf: false
});
msg.channel.send('Success! I\'m now connected and ready to listen.');
var speakers = [];
connection.on('speaking', (user, speaking) => {
// when a user starts speaking:
if (speaking) {
console.log(`${user} started talking`)
// add them to the list of speakers
// start a timer recording how long they're speaking
speakers.push({ user: new Date().getTime() });
// when a user stops speaking:
} else {
console.log(`${user} stopped talking`)
// stop the timer and add it to their total time
var talkTime = (new Date().getTime()) - (stats[user]['timeSpeaking']);
var talkTime = talkTime / 1000;
var talkTime = Math.abs(talkTime);
if (user in stats) {
stats[user]['timeSpeaking'] = stats[user]['timeSpeaking'] + talkTime;
} else {
stats[user] = {
'timeSpeaking': talkTime,
};
};
// remove them from the list of speakers
if (speakers.includes(user)) {
arrayRemove(speakers, user)
};
};
});
connection.on('disconnect', () => {
msg.channel.send('I was disconnected ⁉️.')
.catch(error => {
console.log(error);
});
})
};
suppose we try to connect web socket. web socket server sends some data for fetching client status which represents the online or offline. I tried to store these data into redux (works fine), but I need to change these statuses instantly with overriding the existing objects. I need some functionality for override my redux store. I get so far with the snippet below.
but:
this code push objects to my redux store not overriding
const [status, set_status] = useState([]);
useEffect(() => {
const socket = io(ws_api, {
query: `token=${localStorage.getItem("token")}`,
});
socket.on("message", (data) => {
status.map((item) => {
if (item.application === data.application) {
item["status"] = data.status;
} else {
set_status((status) => [...status, data]);
}
});
});
}, []);
useEffect(() => {
get_client_status(status); // redux action
}, [status]);
the data structure which is coming from the socket on message
{
application: "5ede25f4d3fde1c8a70f0a38"
client: "5ede25f4d3fde1c8a70f0a36"
status: "offline"
}
First search current state for any existing data elements, if found then update it, otherwise add to the array.
Using array::findIndex
const messageHandler = data => {
const dataIndex = status.findIndex(
item => item.application === data.application
);
if (dataIndex !== -1) {
set_status(status =>
status.map(item => {
return item.application === data.application
? { ...item, status: data.status }
: item;
})
);
} else {
set_status(status => [...status, data]);
}
});
Using array::find
const messageHandler = data => {
const found = status.find(item => item.application === data.application);
if (found) {
set_status(status =>
status.map(item => {
return item.application === data.application
? { ...item, status: data.status }
: item;
})
);
} else {
set_status(status => [...status, data]);
}
});
Edit: define this callback outside the effect
useEffect(() => {
socket.on("message", messageHandler);
}, []);
I started integrating websockets into an existing React/Django app following along with this example (accompanying repo here). In that repo, the websocket interface is in websockets.js, and is implemented in containers/Chat.js.
I can get that code working correctly as-is.
I then started re-writing my implementation to use Hooks, and hit a little wall. The data flows through the socket correctly, arrives in the handler of each client correctly, and within the handler can read the correct state. Within that handler, I'm calling my useState function to update state with the incoming data.
Originally I had a problem of my single useState function within addMessage() inconsistently firing (1 in 10 times?). I split my one useState hook into two (one for current message, one for all messages). Now in addMessage() upon receiving data from the server, my setAllMessages hook will only update the client where I type the message in - no other clients. All clients receive/can log the data correctly, they just don't run the setAllMessages function.
If I push to an empty array outside the function, it works as expected. So it seems like a problem in the function update cycle, but I haven't been able to track it down.
Here's my version of websocket.js:
class WebSocketService {
static instance = null;
static getInstance() {
if (!WebSocketService.instance) {
WebSocketService.instance = new WebSocketService();
}
return WebSocketService.instance;
}
constructor() {
this.socketRef = null;
this.callbacks = {};
}
disconnect() {
this.socketRef.close();
}
connect(chatUrl) {
const path = `${URLS.SOCKET.BASE}${URLS.SOCKET.TEST}`;
this.socketRef = new WebSocket(path);
this.socketRef.onopen = () => {
console.log('WebSocket open');
};
this.socketRef.onmessage = e => {
this.socketNewMessage(e.data);
};
this.socketRef.onerror = e => {
console.log(e.message);
};
this.socketRef.onclose = () => {
this.connect();
};
}
socketNewMessage(data) {
const parsedData = JSON.parse(data);
const { command } = parsedData;
if (Object.keys(this.callbacks).length === 0) {
return;
}
Object.keys(SOCKET_COMMANDS).forEach(clientCommand => {
if (command === SOCKET_COMMANDS[clientCommand]) {
this.callbacks[command](parsedData.presentation);
}
});
}
backend_receive_data_then_post_new(message) {
this.sendMessage({
command_for_backend: 'backend_receive_data_then_post_new',
message: message.content,
from: message.from,
});
}
sendMessage(data) {
try {
this.socketRef.send(JSON.stringify({ ...data }));
} catch (err) {
console.log(err.message);
}
}
addCallbacks(allCallbacks) {
Object.keys(SOCKET_COMMANDS).forEach(command => {
this.callbacks[SOCKET_COMMANDS[command]] = allCallbacks;
});
}
state() {
return this.socketRef.readyState;
}
}
const WebSocketInstance = WebSocketService.getInstance();
export default WebSocketInstance;
And here's my version of Chat.js
export function Chat() {
const [allMessages, setAllMessages] = useState([]);
const [currMessage, setCurrMessage] = useState('');
function waitForSocketConnection(callback) {
setTimeout(() => {
if (WebSocketInstance.state() === 1) {
callback();
} else {
waitForSocketConnection(callback);
}
}, 100);
}
waitForSocketConnection(() => {
const allCallbacks = [addMessage];
allCallbacks.forEach(callback => {
WebSocketInstance.addCallbacks(callback);
});
});
/*
* This is the problem area
* `incoming` shows the correct data, and I have access to all state
* But `setAllMessages` only updates on the client I type the message into
*/
const addMessage = (incoming) => {
setAllMessages([incoming]);
};
// update with value from input
const messageChangeHandler = e => {
setCurrMessage(e.target.value);
};
// Send data to socket interface, then to server
const sendMessageHandler = e => {
e.preventDefault();
const messageObject = {
from: 'user',
content: currMessage,
};
setCurrMessage('');
WebSocketInstance.backend_receive_data_then_post_new(messageObject);
};
return (
<div>
// rendering stuff here
</div>
);
}
There is no need to rewrite everything into functional components with hooks.
You should decompose it functionally - main (parent, class/FC) for initialization and providing [data and] methods (as props) to 2 functional childrens/components responsible for rendering list and input (new message).
If you still need it ... useEffect is a key ... as all code is run on every render in functional components ... including function definitions, redefinitions, new refs, duplications in callbacks array etc.
You can try to move all once defined functions into useEffect
useEffect(() => {
const waitForSocketConnection = (callback) => {
...
}
const addMessage = (incoming) => {
setAllMessages([incoming]);
};
waitForSocketConnection(() => {
...
}
}, [] ); // <<< RUN ONCE
I would like create WebRTC connection on 2 devices.
On my first device (Initiator), i create my Offer with this method :
createOffer() {
const { pc } = this;
pc.onnegotiationneeded = () => {
pc.createOffer()
.then(offer => pc.setLocalDescription(offer))
.then(() => {
this.setState({
offer: JSON.stringify(pc.localDescription)
});
});
};
}
And on my second device (Receiver), i create the answer :
createAnswer() {
const { pc } = this;
const { dataOffer } = this.state;
if (dataOffer) {
const sd = new RTCSessionDescription(JSON.parse(dataOffer));
pc.setRemoteDescription(sd)
.then(() => pc.createAnswer())
.then(answer => {
this.setState({
offer: JSON.stringify(answer)
});
return pc.setLocalDescription(answer);
});
}
}
But, after send Answer on first device, i've this error : PeerConnection cannot create an answer in a state other than have-remote-offer or have-local-pranswer.
When Initiator receive the answer, i run createAnswer method with answer data, That may be the problem ?
I don't really understand what method/event i need use after receive the answer :/
EDIT with new method for Initiator device :
receiveAnswer() {
const { pc } = this;
const { dataOffer } = this.state;
const sd = new RTCSessionDescription(JSON.parse(dataOffer));
pc.setRemoteDescription(sd);
}
But the connection state stay on checking :(
You can see my componentDidMount :
componentDidMount() {
const { pc } = this;
pc.oniceconnectionstatechange = () => {
this.setState({
connectionState: pc.iceConnectionState
});
};
pc.onaddstream = ({ stream }) => {
if (stream) {
this.setState({
receiverVideoURL: stream.toURL()
});
}
};
}