ALL ARTICLES
SHARE

Creating a free Raise Hand Extension for Google Meets

author-avatar
Development
13 min read

At Flatirons, we use the “raise your hand” functionality in Google Meets to signify that someone wants to talk in group meetings. However, we recently downgraded our Google Workspace account, and the functionality was lost. It turns out that hand-raising cost the company a few hundred dollars per month, and downgrading our plan removed it. So, I decided to build my own extension that works directly in the Google Workspace Starter plan. Here we will explore how I did that.

Creating a Raise Your Hand Tool for Google Workspace Starter

In this blog, we’re going to make use of Google’s own infrastructure to create a Google Chrome extension that will allow us to raise our hands in a Google Meeting. Google, this functionality is basic and should be included without cost. And now, with a little help from us, it will be. Let’s get started!

Create A Firebase Account

The first step in our journey is to create a free Firebase account. To create your account, go to https://firebase.google.com/ and sign in via google, or create a google account to use with Firebase. Once you have access to Firebase, create a new Firebase project by clicking Get Started.

Create a free app or extension

Provide a name for the project. In this case we’ll use ‘Raise Your Hands’. Click Continue.

Name the project

You will next be asked whether you wish to include Google Analytics. 

Program google analytics for tool creation

Finally, configure Analytics, and click Create Project.

configure google analytics for creating free extension

Once your project is created, you’ll need a Realtime database for our project. In the left-hand menu, under Product categories, expand Build, and select Realtime Database

Firebase Realtime

Click Create Database.

Realtime database

Select a database location and click Next.

Data base selection for free hand extension tool

Finally, select Start in test mode.

Select a data base for your free raise hand extension code

Next, create the application. Select the option to create a web application.

Select option to new application

Enter the name of the app and register the application.

Add free raise hand extension code

Once the app is registered, add the application via npm, and copy the credentials snippet. Click Continue to console

Select free raise hand extension code

Now we are ready to begin coding. As a React.js developer, for this project, we will use React v.18. We’ll create a new project using the command below. 

npx create-react-app raise-your-hands --template typescript

For this project, we will use the Material UI library. Make sure to add the Firebase SDK.

yarn add @mui/material @mui/icons-material @emotion/react @emotion/styled firebase

If you have any questions about the Material UI installation, see the following link:

https://mui.com/pt/material-ui/getting-started/installation/

Let’s create a new component called “HandsUpList”. This component will help us to show the current hands-up list state.

import { Card } from "@mui/material";import Avatar from "@mui/material/Avatar";import List from "@mui/material/List";import ListItem from "@mui/material/ListItem";import ListItemAvatar from "@mui/material/ListItemAvatar";import ListItemButton from "@mui/material/ListItemButton";import ListItemText from "@mui/material/ListItemText";import ListSubheader from "@mui/material/ListSubheader";type Props = { state: string[];};const HandsUpList = ({ state }: Props) => { return (<Card variant="outlined" sx={{ width: "100%", overflow: "hidden" }} >    <List dense sx={{ width: "100%", overflow: "hidden" }} >       <ListSubheader >Hands Up</ListSubheader >       {state. Map((name) = > {         return (           <ListItem key={name} disablePadding >            <ListItemButton >               <ListItemAvatar >                 <Avatar alt={`Avatar ${name}`} >                   {name.substring(0, 2).toUpperCase()}                 </Avatar >               </ListItemAvatar >               <ListItemText                 title={name}                 primary={                  <div                     style={{                       overflow: "hidden",                       textOverflow: "ellipsis",                     }}                    >                     {name}                   </div >                 }               / >             </ListItemButton >           </ListItem >         );       })}     </List >   </Card > );};export default HandsUpList;               

Next, we need to provide the user’s name for use in the list. We will create a dialog component to ask the user for his name, and call it “FormDialog”.

import { useState } from "react";import Button from "@mui/material/Button";import Dialog from "@mui/material/Dialog";import DialogActions from "@mui/material/DialogActions";import DialogContent from "@mui/material/DialogContent";import DialogContentText from "@mui/material/DialogContentText";import DialogTitle from "@mui/material/DialogTitle";import TextField from "@mui/material/TextField";type FormDialogProps = { isOpen: boolean; onClose: () => void; onRaiseMyHand: (name: string) => void;};const FormDialog = ({ isOpen, onClose, onRaiseMyHand }: FormDialogProps) => { const [name, setName] = useState<string>(""); return (   <Dialog open={isOpen} onClose={onClose}>     <DialogTitle>Name</DialogTitle>     <DialogContent>       <DialogContentText>Please enter your name here.</DialogContentText>       <TextField         autoFocus         margin="dense"         required         label="Name"         type="text"         fullWidth         variant="standard"         onChange={(event) => setName(event.target.value)}       />     </DialogContent>     <DialogActions>       <Button onClick={onClose}>Cancel</Button>       <Button disabled={!name} onClick={() => onRaiseMyHand(name)}>         Raise my hand       </Button>     </DialogActions>   </Dialog> );};export default FormDialog;

Now we have to modify our app component. Let’s create two floating action buttons, one to raise the hand and the other to display the hands-up list. Also, we need to ask for the user’s name. Your component should look like this one:

import CloseIcon from "@mui/icons-material/Close";import ListIcon from "@mui/icons-material/List";import PanToolIcon from "@mui/icons-material/PanTool";import { Fab, Tooltip } from "@mui/material";import Box from "@mui/material/Box";import { useCallback, useEffect, useRef, useState } from "react";import FormDialog from "./FormDialog";import HandsUpList from "./HandsUpList";function App() { const nameRef = useRef<string>(""); const [isHandsUp, setIsHandsUp] = useState(false); const [showHandsUpList, setShowHandsUpList] = useState(false); const [showFormDialog, setShowFormDialog] = useState(false); const [handsUpList, setHandsUpList] = useState<string[]>([]); const toggleMyHand = useCallback(() => {   if (!nameRef.current) {     setShowFormDialog(true);     return;   }   const newIsHandsUp = !isHandsUp;   setIsHandsUp(newIsHandsUp); }, [isHandsUp]); const setNameAndRaiseHand = useCallback(   (name: string) => {     setShowFormDialog(false);     nameRef.current = name;     toggleMyHand();   },   [toggleMyHand] ); useEffect(() => {}, []); return (   <Box position="fixed" bottom={100} right={100} zIndex={9999}>     <Tooltip title="Raise your hand">       <Fab         color="primary"         aria-label="Raise your hand"         style={{ marginRight: 10 }}         onClick={() => toggleMyHand()}       >         {isHandsUp ? <CloseIcon /> : <PanToolIcon />}       </Fab>     </Tooltip>     <Fab       color="primary"       aria-label="Hands Up"       onClick={() => setShowHandsUpList(!showHandsUpList)}     >       <ListIcon />     </Fab>     <Box       position="absolute"       bottom={100}       right={0}       width={300}       display={showHandsUpList ? "block" : "none"}     >       <HandsUpList state={handsUpList} />     </Box>     <FormDialog       isOpen={showFormDialog}       onClose={() => setShowFormDialog(false)}       onRaiseMyHand={(name) => setNameAndRaiseHand(name)}     />   </Box> );}export default App;

Done. We now have a simple screen for our app. The app should look like this:

Toolbox free reactions and raise hand extension on googlemeets

The next step is to connect our app with the Firebase real-time database. Create a new file called “firebase.ts” and create two functions, one to update the database state and another one to get the current state:

import { initializeApp } from "firebase/app";import { getDatabase, onValue, ref, set } from "firebase/database";// Your web app's Firebase configurationconst firebaseConfig = { //… Your firebase credentials here};// Initialize Firebaseconst app = initializeApp(firebaseConfig);const database = getDatabase(app);const collection = "raise-your-hands";export const getCurrentState = (onStateUpdated: (state: string[]) => void) => { const dbRef = ref(database, collection); onValue(dbRef, (snapshot) => {   const data = snapshot.val();   onStateUpdated(data); });};export const setCurrentState = (state: string[]) => { set(ref(database, collection), state);};

Finally, getting back to the App component, we need to call getCurrentState on the useEffect hook and setCurrentState on the toggleMyHand callback. Also, we need to manipulate the state of the “handsUpList” in order to not allow duplicate names. It should look like this:

const toggleMyHand = useCallback(() => {   if (!nameRef.current) {     setShowFormDialog(true);     return;   }   const newIsHandsUp = !isHandsUp;   const newState = newIsHandsUp     ? [...handsUpList, nameRef.current]     : handsUpList.filter((n) => n !== nameRef.current);   setCurrentState(newState);   setIsHandsUp(newIsHandsUp); }, [isHandsUp, handsUpList]);
useEffect(() => {   getCurrentState((state) => setHandsUpList(state)); }, []);

Toolbox free raise hand extension

Well done! The app is now linked to the Firebase real-time database. Now we have to change the index.ts of our application (the entry point) to create a new div to render our app inside the Google Meeting session. Check the code:

import ReactDOM from "react-dom/client";import App from "./App";const targetDiv = document.createElement("div");document.body.appendChild(targetDiv);const root = ReactDOM.createRoot(targetDiv as HTMLElement);root. Render(<App / >);

Finally, make a build from your application with the below command:

 yarn build

It will generate a build folder with all of your code like this one:

All that is left to do is to convert it into a Google Chrome extension. Open the manifest.json and change its content with this one:

{ "name": "Raise Your Hands", "description": "Raise Your Hands", "version": "1.0", "manifest_version": 3, "content_scripts": [   {     "matches": ["https://meet.google.com/*"],     "js": ["ENTRY_POINT_FILE_NAME"]   } ], "permissions": []}

For more details about the manifest, you can go through
https://developer.chrome.com/docs/extensions/mv3/manifest/.

Next, replace “ENTRY_POINT_FILE_NAME” with your generated entrypoint file name. Search through the the assets-manifest.json file for “main.js”, and you’ll find it there.

In this example, the main.js file appears as “/static/js/main.f702aa65.js“.  We can use it to replace it inside the manifest.

Let’s see it in action by installing it manually on our Google Chrome: Go to your browser extension page at “chrome://extensions/” and check the developer mode switch, then click on “Load Unpacked” and choose our build folder inside the project folder.

After that, you should see our extension correctly installed.

Select free raise hand extension

And now when you open any Google meeting session, you’ll be able to see our amazing app running inside the meeting session. We have now bypassed Google’s paywall for an app that should be free, using Google’s own infrastructure. If that isn’t worth raising your hands, don’t know what is!

Free raise hand extension

author-avatar
More ideas.
Development

What Is The Fastest Programming Language?

Flatirons

Feb 21, 2024
Development

React.js: Server-Side Rendering vs Client-Side Rendering

Flatirons

Feb 19, 2024
Development

Calculate Absolute Value in Ruby using abs

Flatirons

Feb 17, 2024
Development

Understanding the Fundamental Basics of Redux in State Management

Flatirons

Feb 17, 2024
Development

What is On-Premise Software? A Guide in 2024

Flatirons

Feb 15, 2024
Development

PostgreSQL vs MySQL: Which is Best in 2024?

Flatirons

Feb 14, 2024