My Christmas side project was to develop a small utility, which displays my meetings from our Exchange (on prem) calendar and helps me to convert 'normal' ones to Teams meetings if needed.
1) in Teams there is no 'calendar' view of my day with all the meetings 2) in Outlook Web Access (OWA)
it's not
possible to insert a Teams meeting into a meeting (I know, I know, there is Outlook for Mac, but I'm using OWA)
3) there is no Outlook on Linux, only OWA is available
This task looked like an interesting challenge, not to mention
that for a long time I planned to implement something using Electron.
Let me outline the architecture challenges:
OWA <=> Electron <=> Teams
Agreed, not that much! JavaScript all the way, using node, vanilla JS and some APIs.
List of API's, that I picked for implementation:
Electron - started with the Create a basic application
sample, had an application up and running in no time; well, an empty application. From here on I struggled for a while as I did not
understand how I shall implement things. As for a PoC I already implemented a React UI with an express based nodejs server I tried to
migrate that to Electron. Took time to understand that is not the way, and I shall keep things, simple.
In Electron there are 2 main processes, Application and Renderer. In my case Renderer will render the results from Exchange,
and it communicates with the Application process, which is responsible to communicate with Exchange. Communication between
the 2 processes is done through IPC calls (Inter-Process Communication). So, what I had in React, I moved to HTML + Vanilla JavaScript;
what I had in express, just simply moved to the Application process. From the task list 1-3 was checked ...
Azure - from previous experiences I was pretty sure that I will need to register an application
if I wanna access the MS Graph. I did so, and as previously I've done this using node, I picked Single-page application. But reading through the
documentations, and considering electron, it turned out I'm better off with Mobile and desktop applications!
Why is that? In the Authentication flow, there is a redirect step, and that step for 'Single-page application', requires
a protocol://host:port return URI. On the other hand, 'Mobile and desktop applications' allow custom UIR's so, I registered
'bfcal://auth'. In my Electron application I've created a protocol registration for 'bfcal', and voila things started to fly
... well after few hours of suffering ... task list steps 4-6 completed
// register a 'bfcal' protoclol, so auth flow can return to somewhere // get the access code from the URL and send it down to the page protocol.registerHttpProtocol('bfcal', (req, cb) => { if(mainWindow.webContents) { const receivedHash = `${req.url.split('#')[1]}`; mainWindow.loadURL(url.format({ pathname: path.join(__dirname, 'index.html'), protocol: 'file:', hash: receivedHash, slashes: true })); } })
MS Graph - to access the MS Graph API one needs to provide a Bearer token (access token) in the request Header.
When in the previous step our request is redirected from Azure to the Electron App (previous step), we are authenticated, and
we can request an 'access token'. With this token it's easy now to register a new Teams meeting, in the Graph. For the registration
of the Teams meeting I grab the original meetings (Exchange) Subject, startTime, endTime. On successful registration the Graph API
for Teams returns a JSON structure.
function postMSGraph(endpoint, accessToken, body, callback) { const headers = new Headers(); const bearer = `Bearer ${accessToken}`; headers.append("Authorization", bearer); headers.append('Accept', 'application/json, text/plain') headers.append('Content-Type', 'application/json;charset=UTF-8') const options = { method: "POST", headers: headers, body: JSON.stringify(body) }; // console.log('request made to Graph API at: ' + new Date().toString()); fetch(endpoint, options) .then(response => response.json()) .then(response => callback(response, endpoint)) .catch(error => console.log(error)); }This structure contains a 'joinInformation' html snippet, so with this the original meeting body has to be updated (through EWS in Exchange). Upon successful update, the Electron UI is also refreshed with a link to the newly created meeting ... task list steps 7-9 completed
one more thing - I've done the whole development on a Mac, so I took my other machine with Ubuntu on it, and copied over the source code, installed node+electron and compiled BFCal there as well (obviously this step also implied some suffering). Electron generated an 'rpm' and a 'deb' package that I can share internally with our Linux users ...
Overall, I'm happy with the results, I've learned some new stuff and created my first Electron app.
PS: (notes)