Skip to content

Commit

Permalink
Add dialog during call to request HID permissions;
Browse files Browse the repository at this point in the history
Fix for #1
  • Loading branch information
balansse committed Apr 1, 2022
1 parent 4d9903f commit 8962cfc
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 83 deletions.
8 changes: 3 additions & 5 deletions src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,11 @@
"web_accessible_resources": [
{
"resources": [
"hid_dialog.html",
"content.styles.css",
"jabra.js",
"icon-16.png",
"icon-48.png",
"icon-128.png"
"jabra.js"
],
"matches": []
"matches": ["<all_urls>"]
}
]
}
70 changes: 70 additions & 0 deletions src/pages/Content/content.styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
.jabra-hid-dialog {
background: #fff;
border-radius: 8px;
box-shadow: 0px 1px 2px 0px rgb(60 64 67 / 30%), 0px 2px 6px 2px rgb(60 64 67 / 15%);
right: 50px;
position: absolute;
top: 50px;
width: 360px;
z-index: 1;
font-family: 'Google Sans', Roboto, Arial, sans-serif;
font-size: .875rem;
}

.jabra-hid-dialog .header {
align-items: center;
color: #202124;
display: flex;
padding: 12px 12px 0 24px;
}

.jabra-hid-dialog .header h2 {
font-size: 1.125rem;
font-weight: 400;
letter-spacing: 0;
line-height: 1.5rem;
box-flex: 1;
flex-grow: 1;
}

.jabra-hid-dialog .content {
padding: 0 24px 24px 24px;
}

.jabra-hid-dialog .content .text {
letter-spacing: .01428571em;
font-weight: 400;
line-height: 1.25rem;
color: #3c4043;
margin-bottom: 8px;
}

.jabra-hid-dialog .button {
color: #fff;
background-color: #1a73e8;
padding: 0 16px 0 12px;
margin: 0;
height: 36px;
border-radius: 4px;
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
min-width: 64px;
border: none;
outline: none;
line-height: inherit;
}

.jabra-hid-dialog .button:hover {
background-color: #174ea6;
cursor: pointer;
}

.jabra-hid-dialog .button span {
font-family: 'Google Sans', Roboto, Arial, sans-serif;
letter-spacing: .0107142857em;
font-weight: 500;
text-transform: none;
}
13 changes: 13 additions & 0 deletions src/pages/Content/hid_dialog.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<div class="jabra-hid-dialog">
<div class="header">
<h2>HID Permission required</h2>
</div>
<div class="content">
<div class="text">
Jabra Support extension requires HID permissions to access the Jabra device
</div>
<button class="button">
<span>Request permission</span>
</button>
</div>
</div>
166 changes: 88 additions & 78 deletions src/pages/Content/index.js
Original file line number Diff line number Diff line change
@@ -1,88 +1,75 @@
const JABRA_CONNECTING = "Connecting to Jabra device...";

const MIC_OFF = {
de: "Mikrofon deaktivieren",
en: "Turn off microphone",
ja: "マイクをオフにする",
};

const MIC_ON = {
de: "Mikrofon aktivieren",
en: "Turn on microphone",
ja: "マイクをオンにする",
};

const END_CALL = {
en: "Leave call"
}
const JABRA_PARTNER_KEY = 'fafd-0b91d5be-f026-4198-b8b4-42685884d7ca';
const JABRA_APP_ID = 'chrome-google-meet';
const JABRA_APP_NAME = 'Google Meet Chrome extension';

const JABRA_CONNECTING = 'Connecting to Jabra device...';
const JABRA_HID_CONNECTION_REQUEST = 'Click here to connect to Jabra device';
const JABRA_VENDOR_ID = 2830;

const START_CALL = {
en: "Join now"
en: 'Join now'
}

const currentLanguage = () => document.documentElement.lang;
const oldButtonSelector = (tip) => `[data-tooltip*='${tip}']`;
const buttonSelector = (tip) => `[aria-label*='${tip}']`;

const micOffButtonSelector = () =>
[
"[data-is-muted=false][aria-label*='+ d']",
"[data-is-muted=false][data-tooltip*='+ d']",
buttonSelector(MIC_OFF[currentLanguage()]),
oldButtonSelector(MIC_OFF[currentLanguage()]),
].join(",");
const micOffButton = () => document.querySelector(micOffButtonSelector());

const micOnButtonSelector = () =>
[
"[data-is-muted=true][aria-label*='+ d']",
"[data-is-muted=true][data-tooltip*='+ d']",
buttonSelector(MIC_ON[currentLanguage()]),
oldButtonSelector(MIC_ON[currentLanguage()]),
].join(",");
const micOnButton = () => document.querySelector(micOnButtonSelector());

const endCallSelector = () =>
[
buttonSelector(END_CALL[currentLanguage()]),
oldButtonSelector(END_CALL[currentLanguage()]),
].join(",");
const endCallButton = () => document.querySelector(endCallSelector());

const getElementByIconName = (iconName, parentElement) => {
parentElement = parentElement !== undefined ? parentElement : document;
let element = Array.from(parentElement.querySelectorAll('i.google-material-icons')).find(el => el.textContent === iconName)
return element;
}

const micToggleButton = () => document.querySelector('button [data-icon-type="4"]')?.closest('button');
const endCallButton = () => getElementByIconName('call_end')?.closest('button');
const startCallButton = () => Array.from(document.querySelectorAll('span')).find(el => el.textContent === START_CALL[currentLanguage()]);
//Language-agnostic selectors for Start Call button
//Array.from(document.querySelectorAll('i.google-material-icons')).find(el => el.textContent === "present_to_all")?.closest('div[role="button"]').previousElementSibling
//document.querySelector('[data-is-prejoin]').parentElement.querySelector('[role="button"]')

const avButtonSpan = () => Array.from(document.querySelectorAll('i.google-material-icons')).find(el => el.textContent === "inventory")?.closest('[role="row"]');
const avButtonSpan = () => getElementByIconName('inventory')?.closest('[role="row"]');

var jabraDevice;
let jabraButton = { span: null, button: null, label: null };

(async () => {

const jabraApi = await jabra.init({
transport: jabra.RequestedBrowserTransport.WEB_HID,
partnerKey: "fafd-0b91d5be-f026-4198-b8b4-42685884d7ca",
appId: "chrome-google-meet",
appName: "Google Meet Chrome extension",
partnerKey: JABRA_PARTNER_KEY,
appId: JABRA_APP_ID,
appName: JABRA_APP_NAME,
logger: {
write(logEvent) {
if (logEvent.level === 'error') {
console.log(logEvent.message, logEvent.layer);
}
// Ignore messages with other log levels
}
}
});

if (avButtonSpan() !== undefined) {
jabraButton.span = avButtonSpan().cloneNode(true);
let span = avButtonSpan();

if (span !== undefined) {
jabraButton.span = span.cloneNode(true);
jabraButton.button = jabraButton.span.querySelector('button');
['jscontroller', 'jsaction', 'jsname'].forEach(attribute => jabraButton.button.removeAttribute(attribute));
Array.from(jabraButton.span.querySelectorAll('i.google-material-icons')).find(el => el.textContent === "inventory").innerText = "headset_mic";

getElementByIconName('inventory', jabraButton.span).innerText = 'headset_mic';

jabraButton.button.onclick = async (e) => {
await jabra.webHidPairing();
let connectedDevice = await jabra.webHidPairing();
if (connectedDevice && jabraDevice == undefined) {
jabraButton.label.innerText = JABRA_CONNECTING;
}
};

jabraButton.label = jabraButton.button.lastElementChild;
jabraButton.label.innerText = JABRA_CONNECTING;
let devices = await window.navigator.hid.getDevices();
let jabraHID = devices.find(element => element.vendorId === JABRA_VENDOR_ID);

setTimeout(() => {
if (jabraButton.label.innerText === JABRA_CONNECTING) {
jabraButton.label.innerText = "Click here to connect";
}
}, 5000);
jabraButton.label = jabraButton.button.lastElementChild;
jabraButton.label.innerText = jabraHID ? JABRA_CONNECTING : JABRA_HID_CONNECTION_REQUEST;

avButtonSpan().closest('div').append(jabraButton.span);
span.closest('div').append(jabraButton.span);
}

const callControlFactory = new jabra.CallControlFactory(jabraApi);
Expand All @@ -100,24 +87,28 @@ let jabraButton = { span: null, button: null, label: null };

jabraDevice.deviceSignals.subscribe(
(signal) => {
console.log(signal);
switch (signal.type) {
case jabra.SignalType.PHONE_MUTE:
var button = micOffButton() || micOnButton();
var button = micToggleButton();
button?.click();
break;

case jabra.SignalType.HOOK_SWITCH:
var button = signal.value ? startCallButton() : endCallButton();
button?.click();
break;

case jabra.SignalType.GN_PSEUDO_HOOK_SWITCH:
startCallButton()?.click();
break;
}
}
);

for (const a of document.querySelectorAll('i.google-material-icons')) {
if (a.textContent.includes("call_end")) {
jabraDevice?.offHook(true);
}
//Instant meeting
if (endCallButton()) {
jabraDevice?.offHook(true);
}
});

Expand All @@ -126,7 +117,24 @@ let jabraButton = { span: null, button: null, label: null };
if (change.target.classList.contains('google-material-icons')) {
for (const node of change.addedNodes) {
if (node.nodeType === Node.TEXT_NODE && node.data === 'call_end') {

if (!jabraDevice) {
fetch(chrome.runtime.getURL('/hid_dialog.html'))
.then(r => r.text())
.then(html => {
document.querySelector('c-wiz').insertAdjacentHTML('beforeend', html);

document.querySelector('.jabra-hid-dialog button').onclick = async (e) => {
let connectedDevice = await jabra.webHidPairing();
if (connectedDevice && jabraDevice == undefined) {
document.querySelector('.jabra-hid-dialog').remove();
}
};
});
}

jabraDevice?.offHook(true);

return;
}
}
Expand All @@ -140,25 +148,27 @@ let jabraButton = { span: null, button: null, label: null };

const callEndObserver = new MutationObserver((changes) => {
for (const change of changes) {

for (const node of change.addedNodes) {
if (node.nodeType === Node.TEXT_NODE && node.data === 'You left the meeting') {
jabraDevice?.offHook(false);
return;
if (change.addedNodes.length > 0) {
for (const node of change.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
let callEnded = node.querySelector('div[data-call-ended]')?.dataset.callEnded === 'true';
if (callEnded === true) {
jabraDevice?.offHook(false);
return;
}
}
}
}

}
});
callEndObserver.observe(document.body, {
subtree: true,
childList: true,
childList: true,
subtree: true
});

const muteObserver = new MutationObserver((changes) => {
for (const change of changes) {

if (change.target === micOffButton() || micOnButton() && change.attributeName === "data-is-muted") {
if (change.target === micToggleButton() && change.attributeName === 'data-is-muted') {
jabraDevice?.mute(change.target.dataset.isMuted === 'true');
return;
}
Expand All @@ -169,7 +179,7 @@ let jabraButton = { span: null, button: null, label: null };
subtree: true,
});

window.addEventListener("beforeunload", function () {
window.addEventListener('beforeunload', function () {
jabraDevice?.offHook(false);
jabraDevice?.releaseCallLock();
});
Expand Down
9 changes: 9 additions & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,15 @@ var options = {
},
],
}),
new CopyWebpackPlugin({
patterns: [
{
from: 'src/pages/Content/hid_dialog.html',
to: path.join(__dirname, 'build'),
force: true,
},
],
}),
new CopyWebpackPlugin({
patterns: [
{
Expand Down

0 comments on commit 8962cfc

Please sign in to comment.