Hi Maliming just following up again on this ticket. Thanks
Noted that there is a more minimal signalr.html file is also present in the zip file.
Try this in wwwroot.. You do not need to click any button. In the developer console it should indicate that signalR connected
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebRTC with SignalR Cli Demo</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.7/signalr.min.js"></script>
</head>
<body>
<h1>WebRTC with SignalR</h1>
<div>
<form id="initForm" onsubmit="handleFormSubmit(event)">
<label for="scenarioId">Scenario ID:</label>
<input type="text" id="scenarioId" name="scenarioId" value="3a15abf7-237d-ebab-bdc4-345a639725ce" required>
<label for="personaId">Persona ID:</label>
<input type="text" id="personaId" name="personaId" value="3a15aae8-923b-7cec-53b4-3c98a1d942b8" required>
<button type="submit" id="startWebRTCButton">Start Conversation</button>
</form>
<script>
function handleFormSubmit(event) {
event.preventDefault();
const scenarioId = document.getElementById("scenarioId").value;
const personaId = document.getElementById("personaId").value;
startWebRTC(scenarioId, personaId)
}
</script>
</div>
<ul id="messagesList"></ul>
<script>
const configuration = {};
const _rtc = new RTCPeerConnection(configuration);
let localStream;
let inboundStream = null;
let remoteAudio = null;
const _signalR = new signalR.HubConnectionBuilder()
.withUrl("/signalr-hubs/messaging", { withCredentials: true })
.build();
_signalR.on("ReceiveMessage", (message) => {
console.log(message)
const li = document.createElement("li");
li.textContent = `${tag}: ${message}`;
document.getElementById("messagesList").prepend(li);
});
_signalR.on("ReceiveWebRtcMessage", async (message) => {
console.log("ReceiveWebRtcMessage: ", message);
const messageObj = JSON.parse(message);
if (messageObj.type) {
switch (messageObj.type) {
case 'offer':
// Handle offer
let offerSdp = messageObj.sdp;
console.log("Received sdp offer: ", offerSdp);
// Set the remote description (the offer from the server)
await _rtc.setRemoteDescription(new RTCSessionDescription({ type: 'offer', sdp: offerSdp }));
// Create an answer to the offer
const answer = await _rtc.createAnswer();
// Set the local description with the answer
await _rtc.setLocalDescription(answer);
sendAnswer(answer.sdp);
break;
case 'answer':
let sdp = messageObj.sdp;
console.log("Received sdp answer: ", sdp);
await _rtc.setRemoteDescription(new RTCSessionDescription({ type: 'answer', sdp }));
break;
case 'icecandidate':
console.log("handling icecandidate: ", message);
try {
while (!_rtc.remoteDescription) {
await new Promise(resolve => setTimeout(resolve, 200));
}
const candidateInit = JSON.parse(messageObj.candidate);
await _rtc.addIceCandidate(new RTCIceCandidate(candidateInit));
} catch (e) {
console.error("Error adding received ICE candidate", e);
}
break;
default:
console.warn("Unknown message type:", messageObj.type);
}
} else {
console.warn("Message does not contain a type property");
}
});
_signalR.on("ReceiveOffer", async (offer) => {
console.log("Received SDP offer:", offer);
await _rtc.setRemoteDescription(new RTCSessionDescription({ type: 'offer', offer }));
const answer = await _rtc.createAnswer();
await _rtc.setLocalDescription(answer);
sendAnswer(answer.sdp);
});
_signalR.on("ReceiveAnswer", async (sdp) => {
console.log("Received SDP answer:", sdp);
await _rtc.setRemoteDescription(new RTCSessionDescription({ type: 'answer', sdp }));
});
_signalR.on("ReceiveIceCandidate", async (candidate) => {
console.log("Received ICE candidate:", candidate);
try {
await _rtc.addIceCandidate(new RTCIceCandidate(candidate));
} catch (e) {
console.error("Error adding received ICE candidate", e);
}
});
async function startWebRTC(scenarioId, personaId) {
document.getElementById("startWebRTCButton").disabled = true;
const devices = await navigator.mediaDevices.enumerateDevices();
const audioDevices = devices.filter(device => device.kind === 'audioinput');
if (audioDevices.length === 0) {
throw new Error("No audio input devices found");
}
localStream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
remoteAudio = document.createElement("audio");
remoteAudio.autoplay = true;
document.body.appendChild(remoteAudio);
localStream.getTracks().forEach(track => _rtc.addTrack(track, localStream));
_rtc.onicecandidate = event => {
if (event.candidate) {
console.log("event candidate", event.candidate);
sendIceCandidate(event);
}
};
_rtc.ontrack = (ev) => {
console.log('Received remote audio stream');
console.log("*** ontrack event ***", ev.track);
console.log("*** ontrack event ***", ev.streams);
if (ev.streams && ev.streams[0]) {
remoteAudio.srcObject = ev.streams[0];
} else {
if (!inboundStream) {
inboundStream = new MediaStream();
remoteAudio.srcObject = inboundStream;
}
inboundStream.addTrack(ev.track);
}
};
_rtc.oniceconnectionstatechange = () => {
console.log("ICE connection state:", _rtc.iceConnectionState);
if (_rtc.iceConnectionState === 'connected') {
// Connection is stable and ready
console.log("ICE connection state: connected");
console.log("Initialising conversation...");
initializeConversation({ ScenarioId: scenarioId, PersonaId: personaId });
}
};
_rtc.onnegotiationneeded = async () => {
console.log("Negotiation needed, creating offer");
const offer = await _rtc.createOffer();
await _rtc.setLocalDescription(offer);
sendOffer(offer.sdp);
};
const transcriptionChannel = _rtc.createDataChannel("transcription");
transcriptionChannel.onopen = () => {
console.log("Data channel Transcription opened");
transcriptionChannel.send(JSON.stringify({ type: "transcription", message: "Hello from client" }));
};
transcriptionChannel.onmessage = (event) => {
console.log(`Data channel Transcription: ${event.data}`);
};
transcriptionChannel.onclose = () => {
console.log("Data channel Transcription closed");
}
}
_signalR.start().then(() => {
console.log("SignalR connected");
//startWebRTC();
}).catch(err => console.error(err.toString()));
function sendOffer(sdp) {
console.log("Sending SDP offer:", sdp);
_signalR.invoke("OnReceivedWebRtcMessageAsync", JSON.stringify({ type: "offer", sdp: sdp })).catch(err => console.error(err.toString()));
}
function sendAnswer(sdp) {
console.log("Sending SDP answer:", sdp);
_signalR.invoke("OnReceivedWebRtcMessageAsync", JSON.stringify({ type: "answer", sdp: sdp })).catch(err => console.error(err.toString()));
}
function sendIceCandidate(ev) {
console.log("Sending ICE candidate: ", ev);
_signalR.invoke("OnReceivedWebRtcMessageAsync", JSON.stringify({ type: ev.type, candidate: JSON.stringify(ev.candidate) }))
.catch(err => console.error(err.toString()));
}
function initializeConversation(input) {
_signalR.invoke("InitializeConversation", JSON.stringify(input)).catch(err => console.error(err.toString()));
}
window.onload = () => {
console.log("Window loaded");
};
</script>
</body>
</html>
Our application uses the AbpHub for establishing a SignalR connecdtion with the FE. Behind a corporate firewall, the websockets transport layer is often blocked. The expectation is that SignalR should fallback to a different transport type (ServerSentEvents or LongPolling) however this does not seem to be the case
Using a minimal asp.net core application, the fallback mechanism works. However using a minimal ABP framework project the fallback does not work. Let me know if you need a sample projedt.
Thanks
Thanks so much. I think i've got all the info i need now. as mentioend earlier, we'll raise another ticket on the angular support https://abp.io/qa/questions/8404/3a169e3e-9f81-d082-1017-310a3927bd1e or if you could help get any details on that would be great
Thank you,
Meaning that G provider will apply to all tenants unless overwritten on a T level
This part should be working the same for the cli as well. Can you please share the related package.json that has been generated for the gdpr project? May I also know your cli version?
My main project is on abp 8.x.x. Maybe that's why. Is there a way to download the old gdpr versions?
I'm on ABP CLI 8.3.2
this is the package.json for the code generated:
{ "name": "@volo/abp.ng.gdpr", "version": "9.0.1", "dependencies": { "@abp/ng.components": "~9.0.1", "@abp/ng.theme.shared": "~9.0.1", "@volo/abp.commercial.ng.ui": "^9.0.1", "tslib": "^2.3.0" }, "publishConfig": { "access": "public" }, "homepage": "https://abp.io", "repository": { "type": "git", "url": "https://github.com/volosoft/volo.git" }, "license": "LGPL-3.0", "keywords": [ "aspnetcore", "boilerplate", "framework", "web", "best-practices", "angular", "maui", "blazor", "mvc", "csharp", "webapp" ] }