Basefarm Calendar app

intro

 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.

the rig

reasoning

 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.

design

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:

In my head I sketched the following design:

overview

Considering the design above, here is my todo list:
  1. create an electron app
  2. using EWS connect to OWA
  3. from OWA, get meetings + get meeting details
  4. in Azure AD, register a new app
  5. hook up rights toward MS Graph from the App
  6. implement an auth flow using MSAL (Azure login)
  7. grab an access token, to access Graph API
  8. using Graph API for Teams, create a meeting
  9. use EWS update the meeting with Teams info

(deep) tech details

 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 ...

the rig

 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 ...

conclusion

 Overall, I'm happy with the results, I've learned some new stuff and created my first Electron app.

PS: (notes)

Back to vaew.io