They took Skype from us, Teams sucks
Date:
Today, I'd like to complain about Teams, but to do that, we must first mention Skype!
Skype, it just works
Skype is one of the older communication apps and has been around for a while, it recently turned 20 years old. It's gotten a bunch of updates over the years, some of which were liked more or less than others, but overall a lot of people have been using it to keep in touch. To some degree, that proves that you don't need the rapid growth of the likes of WhatsApp to be successful - instead, sometimes gradual and stable growth is equally as good, as is the staying power to be able to have the product survive for decades.
While, for professional collaboration, options like Slack or Teams are pretty popular, for having group calls or even 1:1 calls, Skype is still pretty good - essentially, it did what Zoom did, just better and earlier. I'm surprised that during the events of 2020 and the subsequent years, Skype didn't see the similar sort of headlines, comparable to those of the explosive growth of Zoom. I can tell you for a fact that some of the teams that I collaborate with have been using Skype for years with no issues, given the limitations of the free version of Zoom and not even having group calls in the free version of Slack. Of course, Skype for Business might be its own unique kind of a mess, but we're not really talking about that today. Regular Skype is just there for us and it works.
Except, no, that's coming to an end. Recently, Microsoft decided that Skype will soon be shut off and people have to migrate over to Teams. Why? Because they said so. Instead of actually putting in the work into keeping the service alive or maybe even improving it, it seems like they've decided to throw in the towel and increase the user numbers of their Teams solution. On some level, sure, having to manage one platform is probably easier for them, but on another it kind of feels like neglecting Skype for years and then trying to sweep it under the rug - why haven't there been aggressive ads for Skype, the same way there have been for Teams? But alas, this is just the situation that we find ourselves in.
It's on the front page of Skype's homepage and everything:
So where does that leave us?
Teams, it just sucks
For what it's worth, migrating over from Skype to Teams actually isn't a difficult process or anything. You can use the same account, your message history is kept and for the most part, you can just keep using it.
Except for the occasional weirdness, such as meetings that are started from Teams do not show up in Skype, which lead to an awkward situation where people were asking me why I'm not joining a call, but I had to share a screenshot in which there is no call - as if I was in a completely different universe.
More serious problems arise, however, once you realize that the features that you used previously won't be available anymore. After this migration, I was presented with the fact that supposedly I have both a work and a personal account under the same e-mail address:
Do I know who made these and when? No, I don't have the slightest idea, it's an e-mail I used for a regular Microsoft account and Skype from way back: The problem is, that neither option really works. For example, if I pick the work account, then I get a warning about not being able to access an organization (what organization?):
On the other hand, with a personal account I can log into the web version of Teams, but not if I install their app locally (like I would with Slack or Skype or whatever), which just throws an error and wants to redirect me to the web based version:
Curiously, I couldn't reproduce this on a completely newly created Microsoft account that has never had Skype, or anything else connected to it. There, the Teams app would just work, which makes me think that my account could just be uniquely screwed up, which doesn't do much when it has a lot of contacts that I need on it. Also, this newly created test account got blocked within a day anyways and I had to verify my phone number to unblock it, how odd.
Before you critique me for being a unique case of some weirdly messed up account structure, remember: Skype had literally no issue with whatever is going on there.
As for Teams not letting me use the desktop app, it doesn't have a good technical reason to be that way. If their local app just wraps the website (maybe with some native integrations sprinkled in), there's no reason why they couldn't just embed it, even in a system native web view or what have you. If it already works for the workplace accounts and works for newly created accounts, then clearly the software itself is capable of running, this is just some weird artificial limitation.
Maybe they're trying to push their users to pay for the business licenses? Maybe not explicitly in this case, though I definitely did see a few of the people that I collaborate with having the same sorts of issues (so my account situation isn't entirely unique) and moving over to the business license would definitely fix whatever this is. That said, it's not like there'd be a good timeframe for being able to fix it and there's more or less nothing that I can do on my own end here.
That was kind of the beauty of Skype: you weren't encumbered with a set number of seats and didn't have to pay for anything. You'd just have an account, would log in with it and that was that. No attempts to upsell you with bundled office products or whatever, just a communication tool you can use. But for now, it seems like the age of freeloading is more or less over:
Regardless, I felt like the limitation is artificial and stupid, so I decided to get some control back over what's running on my PC, since I don't want Teams to be just some browser tab, but rather a program that I can manage separately (such as making it start automatically on system startup on a work computer).
Taking a bit of control back
Doing it isn't actually that difficult. You see, you can basically use Electron to wrap any website that you like and launch it as a separate process, basically an embedded version of Chromium, with a few optional bits of native functionality. Admittedly, I also quite like the idea behind Wails, which does something very similar, except uses the web views available on the system itself, as opposed to making you bundle a whole browser runtime.
Either way, the result is pretty effective, you can have what's essentially the web version on Teams running in a local app:
Furthermore, you can also set the user agent string to whatever you want and it's functionally identical to just running a website, also a bit like how PWAs are supposed to work. The code for it isn't even that complex either!
Here's a config.json
that I extracted:
{
"windowTitle": "Microsoft Teams",
"mainURL": "https://teams.microsoft.com/",
"zoomLevel": 1.00
}
And here's main.js
for Electron:
const { app, BrowserWindow, Notification, screen, desktopCapturer } = require('electron');
const fs = require('fs');
const path = require('path');
// load config.json
const configPath = path.join(__dirname, 'config.json');
let config;
try {
const data = fs.readFileSync(configPath);
config = JSON.parse(data);
} catch (error) {
new Notification({ title: 'Error!', body: 'Error reading config.json: ' + error }).show();
app.quit();
}
function createWindow() {
const configOk = config && config.mainURL;
if (!configOk) {
new Notification({ title: 'Error!', body: 'mainURL is missing in config.json' }).show();
app.quit();
return;
}
// *almost* full size window by default
const primaryDisplay = screen.getPrimaryDisplay();
const { width, height } = primaryDisplay.workAreaSize;
const screenSpaceToLeaveFree = 128; // pixels
const screenWidth = width - screenSpaceToLeaveFree;
const screenHeight = height - screenSpaceToLeaveFree;
// prepare the Electron window
let window = new BrowserWindow({
width: screenWidth,
height: screenHeight,
backgroundColor: '#000000',
webPreferences: {
nodeIntegration: true
},
title: config.windowTitle || 'Electron',
icon: path.join(__dirname, 'images/logo-128.png'),
});
// set Chrome user agent, fake regular web browser (otherwise tries to use IPC, breaks)
const userAgent = '...(put whatever you want here)...';
window.webContents.setUserAgent(userAgent);
// configure session for better compatibility, fake regular web browser (otherwise tries to use IPC, breaks)
const session = window.webContents.session;
// block Teams from detecting Electron, fake regular web browser (otherwise tries to use IPC, breaks)
session.webRequest.onBeforeSendHeaders((details, callback) => {
const { requestHeaders } = details;
if (requestHeaders['User-Agent']) {
requestHeaders['User-Agent'] = userAgent;
}
callback({ requestHeaders });
});
// for screen sharing, allow the user to select their screen
... (see below for an example)
// load the actual page we'll browse
console.log('Loading URL: ', config.mainURL);
window.loadURL(config.mainURL);
// if we need to change the zoom level
if (config.zoomLevel) {
window.webContents.on('did-finish-load', () => {
window.webContents.setZoomFactor(config.zoomLevel);
});
}
}
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
I'm not saying that this is 100% the correct way to do it (since I didn't care much for Electron IPC and just wanted to make the web version to stop trying to use it), but it just goes to show that it's neither super long or complex, nor something that vibe coding with an LLM in an afternoon couldn't do - because that's exactly what I used to make it, alongside a healthy dose of Googling and testing.
There were some odd things along the way that seem new to Windows 11, such as needing to enable some developer features to be able to build the app:
Regardless, you just need Node.js and after that things work just fine:
After building the app, you get a nice installer (Windows is pictured here, other platforms are also supported) that you can either run directly, or send to someone:
Once it installs, it shows up under the installed apps like any other program, so removing it later is pretty trivial, should you need to do so:
In addition to which, the same goes for launching it, it's just there in the start menu.
Now, since I am not a fan of getting a cease & desist in the mail, I'm not really sharing any binaries because there'd clearly be IP issues with using their trademarks, but nobody can prevent me from showing you a screenshot where I used whatever icon or name I please, as well as giving you the source code that I wrote myself, which you can honestly apply to any browser based app.
A while back, I actually used the same approach for getting a cross platform mail client that actually just opens the Nextcloud Mail app underneath.
While I do personally dislike how much Electron seems to be overused (especially given that projects like Wails or Tauri both already use the available system web view components, which most of the time should be used instead to not waste space), this sort of versatility is something that I very much do enjoy. Who knew that actually making apps with Electron (even across multiple platforms) would be a downright pleasant experience?
I feel like if we want to go back to most software being native, the native platforms (or some cross platform solution that transpiles down to native components, a bit like the Lazarus Component Library) need to have the developer experience be equally easy or easier. That's more or less also why Docker became so popular vs something like FreeBSD jails or other methods of containerization. If you want people to use your technology, make it easy and pleasant to use it (or hold them hostage, like Jira does with the industry).
Of course, what I did is a workaround to the desktop app refusing to work, but there were still other issues that remained.
There are still issues
For starters, it seems like Electron is missing a few useful bits, once again for no good reason.
For example, when I attempt to share my screen, there should be a window/screen picker that shows up and lets me choose, what exactly I want to share. Electron has APIs for this, but not the actual visual component which just seems weird. Why would you ship a half baked solution instead of some sensible default that could be replaced with something else for the more custom use cases? Lots of other people have also run into similar issues because of this deficiency.
Essentially, what I had to do in that very same evening, was to hack together a custom window picker:
I didn't want to overcomplicate the code and I didn't care about more specific use cases, like sharing specific windows instead of entire screens, because quite frankly I couldn't be bothered, but at the very least it was doable in the end, even though I really shouldn't have to do something like that in the first place.
Here's the corresponding JavaScript that mostly works:
// for screen sharing, allow the user to select their screen
session.setDisplayMediaRequestHandler((request, callback) => {
// so we only do the callback once
let screenSelectionDone = false;
try {
desktopCapturer.getSources({ types: ['screen'] }).then((sources) => {
try {
// create a selection window for the user to choose a screen
const selectionWindow = new BrowserWindow({
width: 1024,
height: 576,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
},
icon: path.join(__dirname, 'images/logo-128.png'),
modal: true,
parent: BrowserWindow.getFocusedWindow()
});
// load a custom HTML page for screen selection
selectionWindow.loadFile(path.join(__dirname, 'screen-selection.html'));
// pass the sources to the selection window
selectionWindow.webContents.once('did-finish-load', () => {
selectionWindow.webContents.send('screen-sources', sources);
});
// handle the case where the user closes the window without selecting a screen
selectionWindow.on('close', () => {
if (!screenSelectionDone) {
console.log('Selection window closed without selecting a screen.');
new Notification({ title: 'Screen sharing', body: 'No screen selected (closed selection window).' });
screenSelectionDone = true;
callback(null); // no screen selected
}
});
// listen for the user's selection
const { ipcMain } = require('electron');
ipcMain.once('screen-selected', (event, sourceId) => {
const selectedSource = sources.find((source) => source.id === sourceId);
if (selectedSource) {
if (!screenSelectionDone) {
console.log('Selected source: ', selectedSource.name);
new Notification({ title: 'Screen sharing', body: 'Sharing screen: ' + selectedSource.name }).show();
screenSelectionDone = true;
callback({ video: selectedSource });
}
} else {
if (!screenSelectionDone) {
console.log('No screen selected.');
new Notification({ title: 'Screen sharing', body: 'No screen selected.' });
screenSelectionDone = true;
callback(null); // no screen selected
}
}
selectionWindow.close();
});
} catch (error) {
new Notification({ title: 'Error!', body: 'Error in getSources: ' + error }).show();
console.error('Error in getSources:', error);
if (!screenSelectionDone) {
callback(null);
}
}
});
} catch (error) {
new Notification({ title: 'Error!', body: 'Error in setDisplayMediaRequestHandler: ' + error }).show();
console.error('Error in setDisplayMediaRequestHandler:', error);
if (!screenSelectionDone) {
callback(null);
}
}
});
Here's how the HTML for the window looks like:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Select a Screen</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
background-color: #202020;
color: #d8d8d8;
}
.screen {
margin: 10px;
text-align: center;
}
img {
width: 320px;
height: 180px;
cursor: pointer;
border: 2px solid transparent;
}
img:hover {
border: 2px solid blue;
}
</style>
</head>
<body>
<script>
const { ipcRenderer } = require('electron');
ipcRenderer.on('screen-sources', (event, sources) => {
const container = document.body;
sources.forEach((source) => {
const div = document.createElement('div');
div.className = 'screen';
const img = document.createElement('img');
img.src = source.thumbnail.toDataURL();
img.alt = source.name;
img.onclick = () => {
ipcRenderer.send('screen-selected', source.id);
};
const label = document.createElement('p');
label.textContent = source.name;
div.appendChild(img);
div.appendChild(label);
container.appendChild(div);
});
});
</script>
</body>
</html>
The rest of the problems are with Teams itself.
For example, if I create a few accounts and attempt to message myself, sometimes the messages won't arrive (nor will the typing notification show up) until I refresh the page:
Now, initially I thought that it was an issue with the Electron wrapper after doing some further refactoring, since the screen sharing also didn't seem to work.
But no, it was Teams just being plainly broken. You see, I tried using the web based version directly in Edge and it had this exact same issue: screen sharing wouldn't work at all and messages would just be randomly dropped. Then, 30 minutes later, it suddenly started working again in both. That is something that I've never had issues in with neither Skype nor Slack, they just worked.
Here's what I got from the logs in one of those cases:
ERROR:sdp_offer_answer.cc(424)] A BUNDLE group contains a codec collision for payload_type='124. All codecs must share the same type, encoding name, clock rate and parameters. (INVALID_PARAMETER)
ERROR:sdp_offer_answer.cc(3669)] The order of m-lines in subsequent offer doesn't match order from previous offer/answer. (INVALID_PARAMETER)
ERROR:sdp_offer_answer.cc(955)] Failed to set remote offer sdp: The order of m-lines in subsequent offer doesn't match order from previous offer/answer.
That's some great software right there.
There are not that many alternatives
At the end of the day, you might want to throw Teams away in favor of something else.
But here's the thing: there are almost no other free options. For example, I rather like Mattermost because you can self-host it, it has mobile and desktop apps, you can write bots (such as notifications for application uptime), there's even plugins for calls and whatnot, as well as AD/LDAP integration. Except no, not really, unless you pay per seat to actually unlock the functionality:
The software is so good that I could easily imagine replacing Skype for meetings and Slack for general collaboration with it (yaay, free software), even if that meant that I had to convince people at work to open the application to public access (unless people with mobile apps want to connect to the VPN), except the pricing once more gets in the way. I guess the more reasonable thing is to just give up and open our wallets and pay either for the business version of Teams, or just pay for Slack.
When it comes to other free options, I also looked into the likes of Zulip which seems to be fully self-hostable, but you need to pay either for support or if you want mobile notifications, because the way how support for those is coded into mobile OSes is just plain stupid (needing to use APN and FCM) and feels like yet another walled garden:
Zulip’s iOS and Android mobile apps support receiving push notifications from Zulip servers to notify users when new messages have arrived. This is an important feature for having a great mobile app experience.
Google’s and Apple’s security model for mobile push notifications does not allow self-hosted Zulip servers to directly send mobile notifications to the Zulip mobile apps. The Zulip Mobile Push Notification Service solves this problem by forwarding mobile push notifications generated by your server to the Zulip mobile apps.
Furthermore, Zulip doesn't do video calls out of the box either and for that something like self-hosting Jitsi would be needed, which is also a bit of a mess, despite being lovely software otherwise.
Summary
As it stands, Skype is being killed off and an alternative is needed.
Teams kind of sucks unless you pay for it, then it sucks a bit less. Most decent software out there is paid, even Slack. The free options like WhatsApp or Telegram or whatever don't have support for workspaces and channels, like those others do. Even self-hostable software like Mattermost (ignoring the work needed to mitigate security risks) expect you to open your wallet for any remotely enterprise-like feature and other options like Zulip aren't actually free because you can't have proper mobile notifications and you'd also need to configure and host your own call solution etc.
Honestly, the software landscape when it comes to free apps for collaboration sucks when you're a freeloader. Maybe there's some Matrix based option out there, but I haven't looked into those yet. What I'd ideally want is something a bit like what PeerTube has done for hosting videos: a single application that you can deploy for smaller scale teams that does all of the essentials out of the box. No more and no less.
If you're curious, here's my checklist, which is obviously a lot of development work for anyone to make:
- workspaces, channels and threads, as well as DMs
- voice and video calls, both 1:1 and group calls, screen sharing support
- sharing of media and links, inline preview of GIFs and videos
- bot account support and an API for any degree of automation
- invite links and AD/LDAP integration for those who need it (or honestly OIDC/OAuth2 support)
- desktop app, web based app and mobile apps (with push notifications)
- deployable as a single binary or single container, with a single data store (e.g. PostgreSQL/MariaDB/SQLite)
I guess I might keep waiting for a while, clearly the solutions that went anywhere needed to find a way to monetize their labor.
In the mean time, software complexity will eat the world.
Other posts: « Next Previous »