diff --git a/packages/twenty-chrome-extension/src/background/index.ts b/packages/twenty-chrome-extension/src/background/index.ts
index 69ae83cc2d5e..a5d30b2a2f5b 100644
--- a/packages/twenty-chrome-extension/src/background/index.ts
+++ b/packages/twenty-chrome-extension/src/background/index.ts
@@ -1,6 +1,3 @@
-import Crypto from 'crypto-js';
-
-import { exchangeAuthorizationCode } from '~/db/auth.db';
import { isDefined } from '~/utils/isDefined';
// Open options page programmatically in a new tab.
@@ -25,12 +22,6 @@ chrome.runtime.onMessage.addListener((message, _, sendResponse) => {
});
break;
}
- case 'launchOAuth': {
- launchOAuth(({ status, message }) => {
- sendResponse({ status, message });
- });
- break;
- }
case 'openSidepanel': {
chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => {
if (isDefined(tab) && isDefined(tab.id)) {
@@ -57,84 +48,6 @@ chrome.runtime.onMessage.addListener((message, _, sendResponse) => {
return true;
});
-const generateRandomString = (length: number) => {
- const charset =
- 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
- let result = '';
- for (let i = 0; i < length; i++) {
- result += charset.charAt(Math.floor(Math.random() * charset.length));
- }
- return result;
-};
-
-const generateCodeVerifierAndChallenge = () => {
- const codeVerifier = generateRandomString(32);
- const hash = Crypto.SHA256(codeVerifier);
- const codeChallenge = hash
- .toString(Crypto.enc.Base64)
- .replace(/\+/g, '-')
- .replace(/\//g, '_')
- .replace(/=+$/, '');
-
- return { codeVerifier, codeChallenge };
-};
-
-const launchOAuth = (
- callback: ({ status, message }: { status: boolean; message: string }) => void,
-) => {
- const { codeVerifier, codeChallenge } = generateCodeVerifierAndChallenge();
- const redirectUrl = chrome.identity.getRedirectURL();
- chrome.identity
- .launchWebAuthFlow({
- url: `${
- import.meta.env.VITE_FRONT_BASE_URL
- }/authorize?clientId=chrome&codeChallenge=${codeChallenge}&redirectUrl=${redirectUrl}`,
- interactive: true,
- })
- .then((responseUrl) => {
- if (typeof responseUrl === 'string') {
- const url = new URL(responseUrl);
- const authorizationCode = url.searchParams.get(
- 'authorizationCode',
- ) as string;
- exchangeAuthorizationCode({
- authorizationCode,
- codeVerifier,
- }).then((tokens) => {
- if (isDefined(tokens)) {
- chrome.storage.local.set({
- loginToken: tokens.loginToken,
- });
-
- chrome.storage.local.set({
- accessToken: tokens.accessToken,
- });
-
- chrome.storage.local.set({
- refreshToken: tokens.refreshToken,
- });
-
- callback({ status: true, message: '' });
-
- chrome.tabs.query(
- { active: true, currentWindow: true },
- ([tab]) => {
- if (isDefined(tab) && isDefined(tab.id)) {
- chrome.tabs.sendMessage(tab.id, {
- action: 'executeContentScript',
- });
- }
- },
- );
- }
- });
- }
- })
- .catch((error) => {
- callback({ status: false, message: error.message });
- });
-};
-
chrome.tabs.onUpdated.addListener(async (tabId, _, tab) => {
const isDesiredRoute =
tab.url?.match(/^https?:\/\/(?:www\.)?linkedin\.com\/company(?:\/\S+)?/) ||
@@ -154,3 +67,34 @@ chrome.tabs.onUpdated.addListener(async (tabId, _, tab) => {
enabled: true,
});
});
+
+const setTokenStateFromCookie = (cookie: string) => {
+ const decodedValue = decodeURIComponent(cookie);
+ const tokenPair = JSON.parse(decodedValue);
+ if (isDefined(tokenPair)) {
+ chrome.storage.local.set({
+ isAuthenticated: true,
+ accessToken: tokenPair.accessToken,
+ refreshToken: tokenPair.refreshToken,
+ });
+ }
+};
+
+chrome.cookies.onChanged.addListener(async ({ cookie }) => {
+ if (cookie.name === 'tokenPair') {
+ setTokenStateFromCookie(cookie.value);
+ }
+});
+
+// This will only run the very first time the extension loads, after we have stored the
+// cookiesRead variable to true, this will not allow to change the token state everytime background script runs
+chrome.cookies.get(
+ { name: 'tokenPair', url: `${import.meta.env.VITE_FRONT_BASE_URL}` },
+ async (cookie) => {
+ const store = await chrome.storage.local.get(['cookiesRead']);
+ if (isDefined(cookie) && !isDefined(store.cookiesRead)) {
+ setTokenStateFromCookie(cookie.value);
+ chrome.storage.local.set({ cookiesRead: true });
+ }
+ },
+);
diff --git a/packages/twenty-chrome-extension/src/contentScript/index.ts b/packages/twenty-chrome-extension/src/contentScript/index.ts
index 2699d3a1fc22..7bf17bb09458 100644
--- a/packages/twenty-chrome-extension/src/contentScript/index.ts
+++ b/packages/twenty-chrome-extension/src/contentScript/index.ts
@@ -1,5 +1,6 @@
import { insertButtonForCompany } from '~/contentScript/extractCompanyProfile';
import { insertButtonForPerson } from '~/contentScript/extractPersonProfile';
+import { isDefined } from '~/utils/isDefined';
// Inject buttons into the DOM when SPA is reloaded on the resource url.
// e.g. reload the page when on https://www.linkedin.com/in/mabdullahabaid/
@@ -21,3 +22,12 @@ chrome.runtime.onMessage.addListener(async (message, _, sendResponse) => {
sendResponse('Executing!');
});
+
+chrome.storage.local.onChanged.addListener(async (store) => {
+ if (isDefined(store.accessToken)) {
+ if (isDefined(store.accessToken.newValue)) {
+ await insertButtonForCompany();
+ await insertButtonForPerson();
+ }
+ }
+});
diff --git a/packages/twenty-chrome-extension/src/manifest.ts b/packages/twenty-chrome-extension/src/manifest.ts
index 18f21d764c2b..39b15c790fe1 100644
--- a/packages/twenty-chrome-extension/src/manifest.ts
+++ b/packages/twenty-chrome-extension/src/manifest.ts
@@ -2,10 +2,6 @@ import { defineManifest } from '@crxjs/vite-plugin';
import packageData from '../package.json';
-const host_permissions =
- process.env.VITE_MODE === 'development'
- ? ['https://www.linkedin.com/*', 'http://localhost:3001/*']
- : ['https://www.linkedin.com/*'];
const external_sites =
process.env.VITE_MODE === 'development'
? [`https://app.twenty.com/*`, `http://localhost:3001/*`]
@@ -48,9 +44,12 @@ export default defineManifest({
},
],
- permissions: ['activeTab', 'storage', 'identity', 'sidePanel'],
+ permissions: ['activeTab', 'storage', 'identity', 'sidePanel', 'cookies'],
- host_permissions: host_permissions,
+ // setting host permissions to all http connections will allow
+ // for people who host on their custom domain to get access to
+ // extension instead of white listing individual urls
+ host_permissions: ['https://*/*', 'http://*/*'],
externally_connectable: {
matches: external_sites,
diff --git a/packages/twenty-chrome-extension/src/options/Settings.tsx b/packages/twenty-chrome-extension/src/options/Settings.tsx
new file mode 100644
index 000000000000..1df9ae9e65f4
--- /dev/null
+++ b/packages/twenty-chrome-extension/src/options/Settings.tsx
@@ -0,0 +1,91 @@
+import { useEffect, useState } from 'react';
+import styled from '@emotion/styled';
+
+import { TextInput } from '@/ui/input/components/TextInput';
+import { isDefined } from '~/utils/isDefined';
+
+const StyledWrapper = styled.div`
+ align-items: center;
+ background: ${({ theme }) => theme.background.primary};
+ display: flex;
+ height: 100vh;
+ justify-content: center;
+`;
+
+const StyledContainer = styled.div`
+ width: 400px;
+ height: 350px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: ${({ theme }) => theme.spacing(8)};
+`;
+
+const StyledActionContainer = styled.div`
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ justify-content: center;
+ width: 300px;
+`;
+
+const Settings = () => {
+ const [serverBaseUrl, setServerBaseUrl] = useState('');
+ const [clientUrl, setClientUrl] = useState('');
+
+ useEffect(() => {
+ const getState = async () => {
+ const store = await chrome.storage.local.get();
+ if (isDefined(store.serverBaseUrl)) {
+ setServerBaseUrl(store.serverBaseUrl);
+ } else {
+ setServerBaseUrl(import.meta.env.VITE_SERVER_BASE_URL);
+ }
+
+ if (isDefined(store.clientUrl)) {
+ setClientUrl(store.clientUrl);
+ } else {
+ setClientUrl(import.meta.env.VITE_FRONT_BASE_URL);
+ }
+ };
+ void getState();
+ }, []);
+
+ const handleBaseUrlChange = (value: string) => {
+ setServerBaseUrl(value);
+ chrome.storage.local.set({ serverBaseUrl: value });
+ };
+
+ const handleClientUrlChange = (value: string) => {
+ setClientUrl(value);
+ chrome.storage.local.set({ clientUrl: value });
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Settings;
diff --git a/packages/twenty-chrome-extension/src/options/Sidepanel.tsx b/packages/twenty-chrome-extension/src/options/Sidepanel.tsx
index 674a3f799898..7632bda49a09 100644
--- a/packages/twenty-chrome-extension/src/options/Sidepanel.tsx
+++ b/packages/twenty-chrome-extension/src/options/Sidepanel.tsx
@@ -1,9 +1,7 @@
-import { useEffect, useState } from 'react';
+import { useCallback, useEffect, useRef, useState } from 'react';
import styled from '@emotion/styled';
-import { Loader } from '@/ui/display/loader/components/Loader';
import { MainButton } from '@/ui/input/button/MainButton';
-import { TextInput } from '@/ui/input/components/TextInput';
import { isDefined } from '~/utils/isDefined';
const StyledIframe = styled.iframe`
@@ -41,126 +39,73 @@ const StyledActionContainer = styled.div`
`;
const Sidepanel = () => {
- const [isAuthenticating, setIsAuthenticating] = useState(false);
const [isAuthenticated, setIsAuthenticated] = useState(false);
- const [iframeSrc, setIframeSrc] = useState(
+ const [clientUrl, setClientUrl] = useState(
import.meta.env.VITE_FRONT_BASE_URL,
);
- const [error, setError] = useState('');
- const [serverBaseUrl, setServerBaseUrl] = useState('');
- const authenticate = () => {
- setIsAuthenticating(true);
- setError('');
- chrome.runtime.sendMessage(
- { action: 'launchOAuth' },
- ({ status, message }) => {
- if (status === true) {
- setIsAuthenticated(true);
- setIsAuthenticating(false);
- chrome.storage.local.set({ isAuthenticated: true });
- } else {
- setError(message);
- setIsAuthenticating(false);
- }
- },
- );
- };
+ const iframeRef = useRef(null);
+
+ const setIframeState = useCallback(async () => {
+ const store = await chrome.storage.local.get();
+ if (isDefined(store.isAuthenticated)) setIsAuthenticated(true);
+ const { tab: activeTab } = await chrome.runtime.sendMessage({
+ action: 'getActiveTab',
+ });
+
+ if (
+ isDefined(activeTab) &&
+ isDefined(store[`sidepanelUrl_${activeTab.id}`])
+ ) {
+ const url = store[`sidepanelUrl_${activeTab.id}`];
+ setClientUrl(url);
+ } else if (isDefined(store.clientUrl)) {
+ setClientUrl(store.clientUrl);
+ }
+ }, [setClientUrl]);
useEffect(() => {
- const getState = async () => {
+ const initState = async () => {
const store = await chrome.storage.local.get();
- if (isDefined(store.serverBaseUrl)) {
- setServerBaseUrl(store.serverBaseUrl);
- } else {
- setServerBaseUrl(import.meta.env.VITE_SERVER_BASE_URL);
- }
-
- if (store.isAuthenticated === true) setIsAuthenticated(true);
- const { tab: activeTab } = await chrome.runtime.sendMessage({
- action: 'getActiveTab',
- });
-
- if (
- isDefined(activeTab) &&
- isDefined(store[`sidepanelUrl_${activeTab.id}`])
- ) {
- const url = store[`sidepanelUrl_${activeTab.id}`];
- setIframeSrc(url);
- }
+ if (isDefined(store.isAuthenticated)) setIsAuthenticated(true);
+ void setIframeState();
};
- void getState();
+ void initState();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
- const handleBrowserEvents = ({ action }: { action: string }) => {
- if (action === 'changeSidepanelUrl') {
- setIframeSrc('');
- }
- };
- chrome.runtime.onMessage.addListener(handleBrowserEvents);
-
- return () => {
- chrome.runtime.onMessage.removeListener(handleBrowserEvents);
- };
- }, []);
+ void setIframeState();
+ }, [setIframeState, clientUrl]);
useEffect(() => {
- const getIframeState = async () => {
- const store = await chrome.storage.local.get();
- const { tab: activeTab } = await chrome.runtime.sendMessage({
- action: 'getActiveTab',
- });
-
- if (
- isDefined(activeTab) &&
- isDefined(store[`sidepanelUrl_${activeTab.id}`])
- ) {
- const url = store[`sidepanelUrl_${activeTab.id}`];
- setIframeSrc(url);
+ chrome.storage.local.onChanged.addListener((store) => {
+ if (isDefined(store.isAuthenticated)) {
+ if (store.isAuthenticated.newValue === true) {
+ setIframeState();
+ }
}
- };
- void getIframeState();
- }, [iframeSrc]);
-
- const handleBaseUrlChange = (value: string) => {
- setServerBaseUrl(value);
- setError('');
- chrome.storage.local.set({ serverBaseUrl: value });
- };
+ });
+ }, [setIframeState]);
return isAuthenticated ? (
-
+
) : (
- {isAuthenticating ? (
-
- ) : (
-
-
- authenticate()}
- fullWidth
- />
-
- window.open(`${import.meta.env.VITE_FRONT_BASE_URL}`, '_blank')
- }
- fullWidth
- />
-
- )}
+
+ {
+ window.open(clientUrl, '_blank');
+ }}
+ />
+
);