Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extension popup with Sign In / Sign Out #14

Merged
merged 5 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@
"sign:firefox": "cross-var node ./scripts/signFireFoxAddon.mjs \"%npm_package_name%-%npm_package_version%-firefox.zip\" \"%npm_package_version%\"",
"test": "yarn playwright test",
"package": "yarn run package:chromium && yarn run package:firefox",
"package:chromium": "yarn run bundle:chromium && cross-var npx bestzip \"%npm_package_name%-%npm_package_version%-chromium.zip\" dist icons manifest.json LICENSE",
"package:firefox": "yarn run bundle:firefox && cross-var npx bestzip \"%npm_package_name%-%npm_package_version%-firefox.zip\" dist icons manifest.json LICENSE && yarn run sign:firefox",
"package:firefox-no-sign": "yarn run bundle:firefox && cross-var npx bestzip \"%npm_package_name%-%npm_package_version%-firefox.zip\" dist icons manifest.json LICENSE",
"package:chromium": "yarn run bundle:chromium && cross-var npx bestzip \"%npm_package_name%-%npm_package_version%-chromium.zip\" dist static icons manifest.json LICENSE",
"package:firefox": "yarn run bundle:firefox && cross-var npx bestzip \"%npm_package_name%-%npm_package_version%-firefox.zip\" dist static icons manifest.json LICENSE && yarn run sign:firefox",
"package:firefox-no-sign": "yarn run bundle:firefox && cross-var npx bestzip \"%npm_package_name%-%npm_package_version%-firefox.zip\" dist static icons manifest.json LICENSE",
"package-pre": "yarn run patch-pre && yarn run package",
"patch-pre": "node ./scripts/applyPreReleasePatch.js",
"pretty": "prettier --config .prettierrc --loglevel warn --write .",
Expand Down
13 changes: 11 additions & 2 deletions scripts/makeManifest.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,17 @@ const manifestBase = {
48: 'icons/logo-48.png',
128: 'icons/logo-128.png',
},
permissions: ['scripting', 'webNavigation'],
host_permissions: ['*://*.github.com/*', '*://*.gitlab.com/*', '*://*.bitbucket.org/*', '*://*.dev.azure.com/*'],
permissions: ['scripting', 'webNavigation', 'cookies'],
host_permissions: [
'*://*.github.com/*',
'*://*.gitlab.com/*',
'*://*.bitbucket.org/*',
'*://*.dev.azure.com/*',
'*://*.gitkraken.dev/*',
],
action: {
default_popup: 'static/popup.html',
}
};

const getMakeManifest =
Expand Down
143 changes: 143 additions & 0 deletions src/popup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Note: This code runs every time the extension popup is opened.

import { cookies } from 'webextension-polyfill';

interface User {
email: string;
name?: string;
username: string;
}

// Source: https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#basic_example
const sha256 = async (text: string) => {
const encoder = new TextEncoder();
const data = encoder.encode(text);
const hash = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hash));
const hashHex = hashArray
.map((b) => b.toString(16).padStart(2, '0'))
.join('');
return hashHex;
};

const getAccessToken = async () => {
// Attempt to get the access token cookie from GitKraken.dev
const cookie = await cookies.get({
url: 'https://gitkraken.dev',
name: 'accessToken'
});

return cookie?.value;
};

const fetchUser = async () => {
const token = await getAccessToken();
if (!token) {
// The user is not logged in.
return;
}

const res = await fetch('https://api.gitkraken.dev/user', {
headers: {
Authorization: `Bearer ${token}`
}
});

if (!res.ok) {
// The access token is invalid or expired.
return;
}

const user = await res.json();
return user as User;
};

const logout = async () => {
const token = await getAccessToken();
if (!token) {
// The user is not logged in.
return;
}

const res = await fetch('https://api.gitkraken.dev/user/logout', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`
}
});

if (!res.ok) {
// The access token is invalid or expired.
return;
}

// Attempt to clean up the access token cookie from GitKraken.dev
await cookies.remove({
url: 'https://gitkraken.dev',
name: 'accessToken'
});
};

const renderLoggedInContent = async (user: User) => {
const emailHash = await sha256(user.email);

const mainEl = document.getElementById('main-content')!;

const userEl = document.createElement('div');
userEl.classList.add('user');

const gravatarEl = document.createElement('img');
gravatarEl.src = `https://www.gravatar.com/avatar/${emailHash}?s=30&d=retro`;
gravatarEl.alt = user.name || user.username;
gravatarEl.classList.add('avatar');
userEl.appendChild(gravatarEl);

const userInfoEl = document.createElement('div');
userInfoEl.classList.add('user-info');

const userNameEl = document.createElement('div');
userNameEl.textContent = user.name || user.username;
userNameEl.classList.add('user-name');
userInfoEl.appendChild(userNameEl);

const userEmailEl = document.createElement('div');
userEmailEl.textContent = user.email;
userEmailEl.classList.add('user-email');
userInfoEl.appendChild(userEmailEl);

userEl.appendChild(userInfoEl);

mainEl.appendChild(userEl);

const signOutBtn = document.createElement('button');
signOutBtn.textContent = 'Sign out';
signOutBtn.classList.add('btn');
signOutBtn.addEventListener('click', async () => {
await logout();
window.close();
});
mainEl.appendChild(signOutBtn);
};

const renderLoggedOutContent = () => {
const mainEl = document.getElementById('main-content')!;

const signInLink = document.createElement('a');
signInLink.href = 'https://gitkraken.dev/login';
signInLink.target = '_blank';
signInLink.textContent = 'Sign in GitKraken account';
signInLink.classList.add('btn');

mainEl.appendChild(signInLink);
};

const main = async () => {
const user = await fetchUser();
if (user) {
void renderLoggedInContent(user);
} else {
renderLoggedOutContent();
}
};

void main();
54 changes: 54 additions & 0 deletions static/popup.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
}

#main-content {
width: 287px;
padding: 8px;
font-size: 12px;
}

#main-content > *:not(:last-child) {
margin-bottom: 4px;
}

a.btn {
color: black;
text-decoration: none;
display: block;
padding: 5px;
}
button.btn {
background: none;
border: none;
cursor: pointer;
display: block;
width: 100%;
text-align: start;
padding: 5px;
}

img.avatar {
border-radius: 50%;
border: 2px solid #7D868F;
}

.user {
display: flex;
align-items: center;
border: 1px solid #D7D8DB;
border-radius: 6px;
padding: 8px;
}
.user-info {
margin-left: 8px;
}
.user-info .user-name {
font-weight: bold;
}
.user-info .user-email {
font-size: 10px;
}
7 changes: 7 additions & 0 deletions static/popup.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<html>
<script src="/dist/popup.js"></script>
<link rel="stylesheet" href="popup.css" />
<body>
<main id="main-content"></main>
</body>
</html>
1 change: 1 addition & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ function getExtensionConfig(mode, env) {
entry: {
background: './src/background.ts',
'service-worker': './src/service-worker.ts',
popup: './src/popup.ts',
},
mode: mode,
target: 'web',
Expand Down