Agora screen sharing in electron with react, Gives permission denied - reactjs

I am building an electron-react app with Agora for calls. I am trying to implement the screen sharing feature.
As described in the following documentation I tried requesting screen sharing.
https://docs.agora.io/en/video/screensharing_web_ng?platform=Web#screen-sharing-on-electron
But I am getting the following error
AgoraRTCError PERMISSION_DENIED: NotAllowedError: Permission denied
I tried invoking the same function AgoraRTC.createScreenVideoTrack in react and electron but the result was the same.
How do I get permission to share the screen in electron with agora?

After some research, I found the electron way of doing it.
First I request to share the screen.
const turnScreenSharingOn = async () => {
const sources = await window.electron.media.getDesktopSources();
setScreenSources(sources);
setScreenSharePopupVisible(true);
};
window.electron.media.getDesktopSources() is a electron preload function that fetches the screen sources.
getDesktopSources: async () =>
await desktopCapturer
.getSources({
types: ['window', 'screen'],
})
.then((sources) =>
sources.map((source) => ({
id: source.id,
name: source.name,
appIconUrl: source?.appIcon?.toDataURL(),
thumbnailUrl: source?.thumbnail
?.resize({ height: 160 })
.toDataURL(),
}))
)
Once I have the sources I show the popup from which I choose the source and pass the source id to the next function.
const onScreenChoose = async (sourceId: string, cb?: () => void) => {
const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: sourceId,
},
} as MediaTrackConstraints,
});
const track = stream.getVideoTracks()[0];
const screenTrack = AgoraRTC.createCustomVideoTrack({
mediaStreamTrack: track,
});
window.agora.screenTrack?.setEnabled(false);
window.agora.screenTrack = screenTrack;
setScreenTrack(screenTrack);
await screenShareClient.publish(screenTrack);
setScreenSharePopupVisible(false);
cb?.();
};

Related

React Testing Library Unit Test Case: Unable to find node on an unmounted component

I'm having issue with React Unit test cases.
React: v18.2
Node v18.8
Created custom function to render component with ReactIntl. If we use custom component in same file in two different test cases, the second test is failing with below error.
Unable to find node on an unmounted component.
at findCurrentFiberUsingSlowPath (node_modules/react-dom/cjs/react-dom.development.js:4552:13)
at findCurrentHostFiber (node_modules/react-dom/cjs/react-dom.development.js:4703:23)
at findHostInstanceWithWarning (node_modules/react-dom/cjs/react-dom.development.js:28745:21)
at Object.findDOMNode (node_modules/react-dom/cjs/react-dom.development.js:29645:12)
at Transition.performEnter (node_modules/react-transition-group/cjs/Transition.js:280:71)
at node_modules/react-transition-group/cjs/Transition.js:259:27
If I run in different files or test case with setTimeout it is working as expected and there is no error. Please find the other configs below. It is failing even it is same test case.
setUpIntlConfig();
beforeAll(() => server.listen());
afterEach(() => {
server.resetHandlers();
});
afterAll(() => {
jest.clearAllMocks();
server.close();
cleanup();
});
Intl Config:
export const setUpIntlConfig = () => {
if (global.Intl) {
Intl.NumberFormat = IntlPolyfill.NumberFormat;
Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
} else {
global.Intl = IntlPolyfill;
}
};
export const RenderWithReactIntl = (component: any) => {
return {
...render(
<IntlProvider locale="en" messages={en}>
{component}
</IntlProvider>
)
};
};
I'm using msw as mock server. Please guide us, if we are missing any configs.
Test cases:
test('fire get resource details with data', async () => {
jest.spyOn(SGWidgets, 'getAuthorizationHeader').mockReturnValue('test-access-token');
process.env = Object.assign(process.env, { REACT_APP_DIAM_API_ENDPOINT: '' });
RenderWithReactIntl(<AllocatedAccess diamUserId={diamUserIdWithData} />);
await waitForElementToBeRemoved(() => screen.getByText(/loading data.../i));
const viewResource = screen.getAllByText(/view resource/i);
fireEvent.click(viewResource[0]);
await waitForElementToBeRemoved(() => screen.getByText(/loading/i));
const ownerName = screen.getByText(/benedicte masson/i);
expect(ownerName).toBeInTheDocument();
});
test('fire get resource details with data----2', async () => {
jest.spyOn(SGWidgets, 'getAuthorizationHeader').mockReturnValue('test-access-token');
process.env = Object.assign(process.env, { REACT_APP_DIAM_API_ENDPOINT: '' });
RenderWithReactIntl(<AllocatedAccess diamUserId={diamUserIdWithData} />);
await waitForElementToBeRemoved(() => screen.getByText(/loading data.../i));
const viewResource = screen.getAllByText(/view resource/i);
fireEvent.click(viewResource[0]);
await waitForElementToBeRemoved(() => screen.getByText(/loading/i));
const ownerName = screen.getByText(/benedicte masson/i);
expect(ownerName).toBeInTheDocument();
});
Can you try these changes:
test('fire get resource details with data----2', async () => {
jest.spyOn(SGWidgets, 'getAuthorizationHeader').mockReturnValue('test-access-token');
process.env = Object.assign(process.env, { REACT_APP_DIAM_API_ENDPOINT: '' });
RenderWithReactIntl(<AllocatedAccess diamUserId={diamUserIdWithData} />);
await waitFor(() => expect(screen.getByText(/loading data.../i)).not.toBeInTheDocument());
const viewResource = screen.getAllByText(/view resource/i);
act(() => {
fireEvent.click(viewResource[0]);
});
await waitFor(() => expect(screen.getByText(/loading/i)).not.toBeInTheDocument());
expect(screen.getByText(/benedicte masson/i)).toBeVisible();
});
I've got into the habit of using act() when altering something that's visible on the screen. A good guide to here: https://testing-library.com/docs/guide-disappearance/
Using getBy* in the waitFor() blocks as above though, you may be better off specifically checking the text's non-existence.
Without seeing your code it's difficult to go any further. I always say keep tests short and simple, we're testing one thing. The more complex they get the more changes for unforeseen errors. It looks like you're rendering, awaiting a modal closure, then a click, then another modal closure, then more text on the screen. I'd split it into two or more tests.

Integrate DeepAR Shoes try-on SDK with react

I am trying to implement DeepAR SDK for shoe try-on in my react app but when I try to instantiate DeepAR class I got a weird error:
Uncaught (in promise) RuntimeError: Aborted(both async and sync fetching of the wasm failed). Build with -sASSERTIONS for more info.
at q2 (deepar.esm.js:1:676382)
at Q2 (deepar.esm.js:1:676689)
at deepar.esm.js:1:755786
I tried to put those files in public folder, and inside src but it didn't work.
const deepar = new DeepAR({
canvas,
licenseKey: import.meta.env.VITE_DEEPAR_SDK_KEY,
deeparWasmPath: "../lib/wasm/deepar.wasm",
footTrackingConfig: {
poseEstimationWasmPath: "../lib/wasm/libxzimgPoseEstimation.wasm",
detectorPath: iosDetected
? "../lib/models/foot/foot-detector-ios.bin"
: "../lib/models/foot/foot-detector-android.bin",
trackerPath: iosDetected
? "../lib/models/foot/foot-tracker-ios.bin"
: "../lib/models/foot/foot-tracker-android.bin",
objPath: "../lib/models/foot/foot-model.obj",
},
callbacks: {
onInitialize: () => {
deepar.startVideo();
// deepar.switchEffect(0, "mask", "/effects/Shoe_PBR");
},
onCameraPermissionAsked: () => console.log("onCameraPermissionAsked"),
onCameraPermissionGranted: () => console.log("onCameraPermissionGranted"),
onCameraPermissionDenied: () => console.log("onCameraPermissionDenied"),
onVideoStarted: () => console.log("onVideoStarted"),
onError: (error) => console.log("onError", error),
},
});

How to replace a loading image in a chat with an image uploaded to Firebase Storage using ReactJS?

I have built a chat using Firebase and ReactJS. I mainly followed their Firebase's web codelab at https://firebase.google.com/codelabs/firebase-web#1. However, I have gotten stuck on the image uploading functionality. Since I am using ReactJS, I have had to modify their plain JS code to match mine. I am able to save a message with a "loading" image url in Firestore, then, I successfully save the image that I want to ultimately show in the chat in Firebase Storage, and finally then I successfully retrieve its url from Storage and replace it with the url of the loading image in Firestore. The image does show in the chat, however, the loading image is not actually replaced but, instead, it remains in the chat when I want it to be completely replaced, obviously, so that the loading image is no longer there. Here's what I mean, in this image:
As you can see the loading image on top stayed on instead of being replaced by the image underneath it. I think it should be filtered out somehow before I save the new snapshot with the new image url. However, I can not figure out how to do it correctly. I tried to filter it out based on the url of the loading image which is saved locally but since it is saved as a base64 in Storage, it did not work. Neither did using the actual Base64 code as a way to filter it out. So, I need help to solve this issue. The codelab does not really specify this nor is it clear how they do it in their code which is in plain Javascript anyways and I use ReactJS so it may not be 100% suitable.
Here's, I believe, enough code to see what is going on. Let me know if you need more of it.
Here's how I send images to the Chat: (modeled on the Firebase codelab)
sendImageToChat () {
this.state.chatFiles.forEach((file) => {
firebase.firestore().collection('Chats')
.doc(this.state.uid)
.collection('Messages')
.add({
docId: this.state.docId,
imageUrl: loadingLogo,
timestamp: new Date(),
uid: this.state.uid,
name: this.state.displayName,
email: this.state.email
})
.catch((error) => {
this.setState({ writeError: error.message });
})
.then((messageRef) => {
// 2 - Upload the image to Cloud Storage.
const filePath = `users/${this.state.displayName}/${this.state.uid}/${moment().format("MMM Do YY")}/${uuidv4()}/${file.name}`
return firebase.storage().ref(filePath).put(file).then((fileSnapshot) => {
// 3 - Generate a public URL for the file.
return fileSnapshot.ref.getDownloadURL().then((url) => {
// 4 - Update the chat message placeholder with the image's URL.
return messageRef.update({
imageUrl: url,
storageUri: fileSnapshot.metadata.fullPath
});
});
});
}).catch(function(error) {
console.error('There was an error uploading a file to Cloud Storage:', error);
});
})
this.setState({
chatFiles: []
})
document.getElementById('file-1').value = "";
}
Here's how I, then, setState when the loading image is added and then when its url is modified: (Notice how I try to filter out the loadingLogo which is the loading image out of the state but it does not obviously work for the reason explained above).
startChat () {
document.getElementById("myForm").style.display = "block";
const ref = firebase.firestore().collection('Chats').doc(this.state.uid).collection('Messages');
const query = ref.orderBy('timestamp', 'desc').limit(10)
this.unsubFromMessages = query.onSnapshot((snapshot) => {
if (snapshot.empty) {
console.log('No matching documents.');
firebase.firestore().collection('Chats').doc(this.state.uid).
set({
name: this.state.displayName,
uid: this.state.uid,
email: this.state.email
}).then(console.log("info saved"))
.catch((error) => {
console.log("Error saving info to document: ", error);
});
}
snapshot.docChanges().reverse().forEach((change) => {
if (change.type === 'removed') {
console.log(change.doc.data().content)
} else if (change.type === 'added') {
this.setState(state => {
const messages = [...state.messages, {id: change.doc.id, body: change.doc.data()}]
return {
messages
}
})
setTimeout( this.scrollToBottom(), 2000)
} else if (change.type === 'modified') {
const filteredMessages = this.state.messages.filter(message => message.imageUrl !== loadingLogo)
console.log(filteredMessages)
this.setState(state => {
const messages = [...filteredMessages, {id: change.doc.id, body: change.doc.data()}]
return {
messages
}
})
setTimeout( this.scrollToBottom(), 2000)
}
});
}, (error) => {console.log(error)});
}
This is part of the Chat's JSX:
<div className="chatArea" id='messages'>
{
this.state.messages.map((message, index) => {
return message.body.uid === this.state.uid
?
<div>
{
message.body.imageUrl ?
<img src={message.body.imageUrl} className="message-sent"></img>
:
<p className="message-sent" key={index}>{message.body.content}</p>
}
</div>
:
<p className="message-received" key={index}>{message.body.content}</p>
})
}
<div style={{ float:"left", clear: "both" }}
ref={(el) => { this.myRef = el; }}>
</div>
</div>
I know the issue is not with Firebase but rather with ReactJS. I know I need to remove, filter out, replace or delete that loading image before or after the modified message with the new url is saved to the state. So, please help me figure this out. I am sure many people may encounter this problem.
Thank you!
I figured it out. I might as well delete this question but it may help someone build a chat with ReactJS and Firebase. Anyways, my approach to filter out based on the object property, imageUrl is a viable option. It works! My silly oversight was that I did not add the parent property or object, "body", after the object "message". More specifically, instead of const filteredMessages = this.state.messages.filter(message => message.imageUrl !== loadingLogo), it should be const filteredMessages = this.state.messages.filter(message => message.body.imageUrl !== loadingLogo). You can also try to add an object property that you can use to filter out messages with, for example, allowed: yes or no. If you need more clarification, just ask me, I am glad to help. Happy coding!

How to refresh powerbi-client-react component once token has expired?

I am testing a react web app where I can display reports from Power BI. I am using powerbi-client-react to embed the reports. However, I face an issue when I load the component with an expired token, I get this error: Content not available screenshot.
So whenever that happens, I catch it with the error event handler, get a new token and update the powerbi report accessToken. However, it doesn't seem to reload/refresh the embed when I set the new accessToken in react. It only displays the screenshot above.
Error log screenshot.
Is there a way to force refresh the embed component with the new access token? or is my approach not correct? Any mistakes pointer would be appreciated.
import React from 'react';
import {models} from 'powerbi-client';
import {PowerBIEmbed} from 'powerbi-client-react';
// Bootstrap config
let embedConfigTest = {
type: 'report', // Supported types: report, dashboard, tile, visual and qna
id: reportId,
embedUrl: powerBIEmbedURL,
accessToken: null,
tokenType: models.TokenType.Embed,
pageView: 'fitToWidth',
settings: {
panes: {
filters: {
expanded: false,
visible: false,
},
},
background: models.BackgroundType.Transparent,
},
};
const PowerBiReport = ({graphName, ...props}) => {
let [embedToken, setEmbedToken] = React.useState();
let [embedConfig, setEmbedConfig] = React.useState(embedConfigTest);
React.useEffect(
() => {
setEmbedToken(EXPIRED_TOKEN);
setEmbedConfig({
...embedConfig,
accessToken: EXPIRED_TOKEN, // Initiate with known expired token
});
},
[graphName]
);
const changeSettings = (newToken) => {
setEmbedConfig({
...embedConfig,
accessToken: newToken,
});
};
// Map of event handlers to be applied to the embedding report
const eventHandlersMap = new Map([
[
'loaded',
function() {
console.log('Report has loaded');
},
],
[
'rendered',
function() {
console.log('Report has rendered');
},
],
[
'error',
async function(event, embed) {
if (event) {
console.error(event.detail);
console.log(embed);
// Simulate getting a new token and update
setEmbedToken(NEW_TOKEN);
changeSettings(NEW_TOKEN);
}
},
],
]);
return (
<PowerBIEmbed
embedConfig={embedConfig}
eventHandlers={eventHandlersMap}
cssClassName={'report-style-class'}
/>
);
};
export default PowerBiReport;
Thanks #vtCode. Here is a sample but the refresh can only happen in 15 secs interval.
import { PowerBIEmbed } from "powerbi-client-react";
export default function PowerBiContainer({ embeddedToken }) {
const [report, setReport] = useState(null);
useEffect(() => {
if (report == null) return;
report.refresh();
}, [report, embeddedToken]);
return (
<PowerBIEmbed
embedConfig={{ ...embedConfig, accessToken: embeddedToken }}
getEmbeddedComponent={(embeddedReport) => setReport(embeddedReport)};
/>
);
}
Alternatively, you can add the React "key" attribute which remounts the component when embededToken changes
<PowerBIEmbed key={embeddedToken}
embedConfig={{ ...embedConfig, accessToken: embeddedToken }}
/>
I ended up solving this issue, although not so beautiful.
I checked the powerbi-client wiki as it has dependency on it and found out that you could use embed.reload() in the embed object I get from the error function.
For some reason (I could not find out why), the error handler gets triggered twice, so to avoid refreshing the token twice, I had to create a dialog notifying the user that the token had expired and whenever that dialog is closed, I reload the powerbi report.
Exact wiki reference:
Overriding Error Experience
Reload a report
Update embed token

React dropzone uploader with meteor gives error_upload though image preview appears OK

I'm trying to upload image files to an AWS S3 bucket, in my Meteor / React app.
I'm using React Dropzone Uploader.
Versions:
METEOR#1.8.1
"react": "^16.11.0"
"react-dropzone-uploader": "^2.11.0"
Everything except the S3 bucket itself is running on my local development machine.
When I drag an image onto the dropzone uploader in the browser, I see the progress bar and then the image preview, but the console shows the message "error_upload" and the "Submit" button is disabled.
According to the docs error_upload is set if upload response has HTTP status code >= 400, but I don't understand what that means. The image preview looks fine, the url has been generated correctly, and I haven't yet tried to perform the actual upload to AWS.
As far as I can see everything is happening in the right order: the client console logs my signedUrl in this form:
signedUrl in client https://my-bucket.s3.amazonaws.com/test/DA8acSgM3wQtrReEK/userpic.jpg?AWSAccessKeyId=somekeyid&Expires=1576600622&Signature=somestuff
before then logging 'uploading' and displaying what look like valid object details:
height: 100
​id: "1576600322132-0"
​lastModifiedDate: "2014-01-21T17:00:42.000Z"
​name: "userpic.jpg"
​percent: 0
​previewUrl: "blob:http://127.0.0.1:3000/80aaa9bd-b7b8-492e-8e13-8c8b971e4998"
​size: 5203
​status: "uploading"
​type: "image/jpeg"
​uploadedDate: "2019-12-17T16:32:02.137Z"
​width: 100
then finally the client console shows:
error_upload
height: 100
​id: "1576600322132-0"
​lastModifiedDate: "2014-01-21T17:00:42.000Z"
​name: "userpic.jpg"
​percent: 100
​previewUrl: "blob:http://127.0.0.1:3000/80aaa9bd-b7b8-492e-8e13-8c8b971e4998"
​size: 5203
​status: "error_upload"
​type: "image/jpeg"
​uploadedDate: "2019-12-17T16:32:02.137Z"
​width: 100
Here's my code:
client:
import React from 'react';
import 'react-dropzone-uploader/dist/styles.css';
import Dropzone from 'react-dropzone-uploader';
const callWithPromise = (method, myParameters) => {
return new Promise((resolve, reject) => {
Meteor.call(method, myParameters, (err, res) => {
if (err) reject('Something went wrong');
resolve(res);
});
});
};
const DropzoneUploader = ({ docId }) => {
const getUploadParams = async ({ meta }) => {
const signedUrl = await callWithPromise('getUploadParams', { 'key': `test/${docId}/${meta.name}` });
console.log('signedUrl in client', signedUrl);
return { 'url': signedUrl };
};
// called every time a file's `status` changes
const handleChangeStatus = ({ meta, file }, status) => { console.log(status, meta, file); };
// receives array of files that are done uploading when submit button is clicked
const handleSubmit = (files, allFiles) => {
console.log(files.map((f) => f.meta));
allFiles.forEach((f) => f.remove());
};
return (
<Dropzone
getUploadParams={getUploadParams}
onChangeStatus={handleChangeStatus}
onSubmit={handleSubmit}
accept="image/*,audio/*,video/*"
/>
);
};
export default DropzoneUploader;
Server: aws.js
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
AWS.config.update({ 'accessKeyId': accessKeyId, 'secretAccessKey': secretAccessKey });
const myBucket = process.env.AWS_BUCKET;
const signedUrlExpireSeconds = 60 * 5;
const getSignedUrl = (key) => s3.getSignedUrl('putObject', {
'Bucket': myBucket,
'Key': key,
'Expires': signedUrlExpireSeconds,
});
export default getSignedUrl;
server: methods.js
Meteor.methods({
'getUploadParams': function ({ key }) {
const signedUrl = getSignedUrl(key);
console.log('signedUrl on server', signedUrl);
return signedUrl;
},
});
CORS configuration on bucket:
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>PUT</AllowedMethod>
<AllowedMethod>POST</AllowedMethod>
<AllowedMethod>GET</AllowedMethod>
<AllowedMethod>HEAD</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
I'll be very grateful for any help as to what I'm doing wrong. Thanks!
Update: the Network tab is showing Status code 403 forbidden 'SignatureDoesNotMatch' for a call to my signedUrl, which surprised me since I didn't think it would try to upload the file until the user clicks "Submit" in the browser. So maybe the problem is with my AWS credentials? But I'd like to know exactly what is happening with the url request at this stage.
Update 2: I've changed my s3.getSignedUrl function to specify 'putObject' instead of 'getObject', but it's still not working. I can see from the Network tab that the request is actually being sent as POST but I can't figure out how to change that?

Resources