mirror of
https://github.com/lingble/twenty.git
synced 2025-11-26 19:04:58 +00:00
fix: user has to login every time chrome sidepanel is opened (#5544)
We can pass the auth tokens to our front app via post message, which will also allow us to pass route names to navigate on it
This commit is contained in:
@@ -30,17 +30,6 @@ chrome.runtime.onMessage.addListener((message, _, sendResponse) => {
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'changeSidepanelUrl': {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => {
|
||||
if (isDefined(tab) && isDefined(tab.id)) {
|
||||
chrome.tabs.sendMessage(tab.id, {
|
||||
action: 'changeSidepanelUrl',
|
||||
message,
|
||||
});
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -82,7 +71,15 @@ const setTokenStateFromCookie = (cookie: string) => {
|
||||
|
||||
chrome.cookies.onChanged.addListener(async ({ cookie }) => {
|
||||
if (cookie.name === 'tokenPair') {
|
||||
setTokenStateFromCookie(cookie.value);
|
||||
const store = await chrome.storage.local.get(['clientUrl']);
|
||||
const clientUrl = isDefined(store.clientUrl)
|
||||
? store.clientUrl
|
||||
: import.meta.env.VITE_FRONT_BASE_URL;
|
||||
chrome.cookies.get({ name: 'tokenPair', url: `${clientUrl}` }, (cookie) => {
|
||||
if (isDefined(cookie)) {
|
||||
setTokenStateFromCookie(cookie.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ export const createDefaultButton = (
|
||||
padding: '0 1rem',
|
||||
cursor: 'pointer',
|
||||
height: '32px',
|
||||
width: 'max-content',
|
||||
};
|
||||
|
||||
Object.assign(div.style, divStyles);
|
||||
|
||||
@@ -75,9 +75,7 @@ export const addCompany = async () => {
|
||||
const companyId = await createCompany(companyInputData);
|
||||
|
||||
if (isDefined(companyId)) {
|
||||
await changeSidePanelUrl(
|
||||
`${import.meta.env.VITE_FRONT_BASE_URL}/object/company/${companyId}`,
|
||||
);
|
||||
await changeSidePanelUrl(`/object/company/${companyId}`);
|
||||
}
|
||||
|
||||
return companyId;
|
||||
@@ -86,16 +84,15 @@ export const addCompany = async () => {
|
||||
export const insertButtonForCompany = async () => {
|
||||
const companyButtonDiv = createDefaultButton('twenty-company-btn');
|
||||
|
||||
const parentDiv: HTMLDivElement | null = document.querySelector(
|
||||
'.org-top-card-primary-actions__inner',
|
||||
const companyDiv: HTMLDivElement | null = document.querySelector(
|
||||
'.org-top-card__primary-content',
|
||||
);
|
||||
|
||||
if (isDefined(parentDiv)) {
|
||||
if (isDefined(companyDiv)) {
|
||||
Object.assign(companyButtonDiv.style, {
|
||||
marginLeft: '.8rem',
|
||||
marginTop: '.4rem',
|
||||
marginTop: '.8rem',
|
||||
});
|
||||
parentDiv.prepend(companyButtonDiv);
|
||||
companyDiv.parentElement?.append(companyButtonDiv);
|
||||
}
|
||||
|
||||
const companyButtonSpan = companyButtonDiv.getElementsByTagName('span')[0];
|
||||
@@ -104,19 +101,16 @@ export const insertButtonForCompany = async () => {
|
||||
const openCompanyOnSidePanel = (companyId: string) => {
|
||||
companyButtonSpan.textContent = 'View in Twenty';
|
||||
companyButtonDiv.onClickHandler(async () => {
|
||||
await changeSidePanelUrl(
|
||||
`${import.meta.env.VITE_FRONT_BASE_URL}/object/company/${companyId}`,
|
||||
);
|
||||
await changeSidePanelUrl(`/object/company/${companyId}`);
|
||||
chrome.runtime.sendMessage({ action: 'openSidepanel' });
|
||||
});
|
||||
};
|
||||
|
||||
if (isDefined(company)) {
|
||||
await changeSidePanelUrl(
|
||||
`${import.meta.env.VITE_FRONT_BASE_URL}/object/company/${company.id}`,
|
||||
);
|
||||
await changeSidePanelUrl(`/object/company/${company.id}`);
|
||||
if (isDefined(company.id)) openCompanyOnSidePanel(company.id);
|
||||
} else {
|
||||
await changeSidePanelUrl(`/objects/companies`);
|
||||
companyButtonSpan.textContent = 'Add to Twenty';
|
||||
|
||||
companyButtonDiv.onClickHandler(async () => {
|
||||
|
||||
@@ -86,9 +86,7 @@ export const addPerson = async () => {
|
||||
const personId = await createPerson(personData);
|
||||
|
||||
if (isDefined(personId)) {
|
||||
await changeSidePanelUrl(
|
||||
`${import.meta.env.VITE_FRONT_BASE_URL}/object/person/${personId}`,
|
||||
);
|
||||
await changeSidePanelUrl(`/object/person/${personId}`);
|
||||
}
|
||||
|
||||
return personId;
|
||||
@@ -98,15 +96,13 @@ export const insertButtonForPerson = async () => {
|
||||
const personButtonDiv = createDefaultButton('twenty-person-btn');
|
||||
|
||||
if (isDefined(personButtonDiv)) {
|
||||
const addedProfileDiv: HTMLDivElement | null = document.querySelector(
|
||||
'.pv-top-card-v2-ctas__custom',
|
||||
);
|
||||
const addedProfileDiv = document.querySelector('.artdeco-card > .ph5');
|
||||
|
||||
if (isDefined(addedProfileDiv)) {
|
||||
Object.assign(personButtonDiv.style, {
|
||||
marginRight: '.8rem',
|
||||
marginTop: '.8rem',
|
||||
});
|
||||
addedProfileDiv.prepend(personButtonDiv);
|
||||
addedProfileDiv.append(personButtonDiv);
|
||||
}
|
||||
|
||||
const personButtonSpan = personButtonDiv.getElementsByTagName('span')[0];
|
||||
@@ -115,19 +111,16 @@ export const insertButtonForPerson = async () => {
|
||||
const openPersonOnSidePanel = (personId: string) => {
|
||||
personButtonSpan.textContent = 'View in Twenty';
|
||||
personButtonDiv.onClickHandler(async () => {
|
||||
await changeSidePanelUrl(
|
||||
`${import.meta.env.VITE_FRONT_BASE_URL}/object/person/${personId}`,
|
||||
);
|
||||
await changeSidePanelUrl(`/object/person/${personId}`);
|
||||
chrome.runtime.sendMessage({ action: 'openSidepanel' });
|
||||
});
|
||||
};
|
||||
|
||||
if (isDefined(person)) {
|
||||
await changeSidePanelUrl(
|
||||
`${import.meta.env.VITE_FRONT_BASE_URL}/object/person/${person.id}`,
|
||||
);
|
||||
await changeSidePanelUrl(`/object/person/${person.id}`);
|
||||
if (isDefined(person.id)) openPersonOnSidePanel(person.id);
|
||||
} else {
|
||||
await changeSidePanelUrl(`/objects/people`);
|
||||
personButtonSpan.textContent = 'Add to Twenty';
|
||||
personButtonDiv.onClickHandler(async () => {
|
||||
personButtonSpan.textContent = 'Saving...';
|
||||
|
||||
@@ -5,10 +5,23 @@ 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/
|
||||
// await insertButtonForCompany();
|
||||
(async () => {
|
||||
await insertButtonForCompany();
|
||||
await insertButtonForPerson();
|
||||
})();
|
||||
|
||||
const companyRoute = /^https?:\/\/(?:www\.)?linkedin\.com\/company(?:\/\S+)?/;
|
||||
const personRoute = /^https?:\/\/(?:www\.)?linkedin\.com\/in(?:\/\S+)?/;
|
||||
|
||||
const executeScript = async () => {
|
||||
const loc = window.location.href;
|
||||
switch (true) {
|
||||
case companyRoute.test(loc):
|
||||
await insertButtonForCompany();
|
||||
break;
|
||||
case personRoute.test(loc):
|
||||
await insertButtonForPerson();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// The content script gets executed upon load, so the the content script is executed when a user visits https://www.linkedin.com/feed/.
|
||||
// However, there would never be another reload in a single page application unless triggered manually.
|
||||
@@ -16,8 +29,7 @@ import { isDefined } from '~/utils/isDefined';
|
||||
// e.g. create "Add to Twenty" button when a user navigates to https://www.linkedin.com/in/mabdullahabaid/ from https://www.linkedin.com/feed/
|
||||
chrome.runtime.onMessage.addListener(async (message, _, sendResponse) => {
|
||||
if (message.action === 'executeContentScript') {
|
||||
await insertButtonForCompany();
|
||||
await insertButtonForPerson();
|
||||
await executeScript();
|
||||
}
|
||||
|
||||
sendResponse('Executing!');
|
||||
@@ -26,8 +38,7 @@ chrome.runtime.onMessage.addListener(async (message, _, sendResponse) => {
|
||||
chrome.storage.local.onChanged.addListener(async (store) => {
|
||||
if (isDefined(store.accessToken)) {
|
||||
if (isDefined(store.accessToken.newValue)) {
|
||||
await insertButtonForCompany();
|
||||
await insertButtonForPerson();
|
||||
await executeScript();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
const btn = document.getElementById('twenty-settings-btn');
|
||||
if (!isDefined(btn)) {
|
||||
const div = document.createElement('div');
|
||||
const img = document.createElement('img');
|
||||
img.src =
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACb0lEQVR4nO2VO4taQRTHr3AblbjxEVlwCwVhg7BoqqCIjy/gAyyFWNlYBOxsfH0KuxgQGwXRUkGuL2S7i1barGAgiwbdW93SnGOc4BonPiKahf3DwXFmuP/fPM4ZlvmlTxAhCBdzHnEQWYiv7Mr4C3NeuVYhQYDPzOUUQgDLBQGcLHNhvQK8DACPx8PTxiqVyvISG43GbyaT6Qfpn06n0m63e/tPAPF4vJ1MJu8kEsnWTCkWi1yr1RKGw+GDRqPBOTfr44vFQvD7/Q/lcpmaaVQAr9fLp1IpO22c47hGOBz+MB6PH+Vy+VYDAL8qlUoGtVotzOfzq4MAgsHgE/6KojiQyWR/bKVSqbSszHFM8Pl8z1YK48JsNltCOBwOnrYLO+8AAIjb+nHbycoTiUQfDJ7tFq4YAHiVSmXBxcD41u8flQU8z7fhzO0r83atVns3Go3u9Xr9x0O/RQXo9/tsIBBg6vX606a52Wz+bZ7P5/WwG29gxSJzhKgA6XTaDoFNF+krFAocmC//4yWEcSf2wTm7mCO19xFgSsKOLI16vV7b7XY7mRNoLwA0JymJ5uQIzgIAuX5PzDElT2m+E8BqtQ4ymcx7Yq7T6a6ZE4sKgOadTucaCwkxp1UzlEKh0GDxIXOwDWHAdi6Xe3swQDQa/Q7mywoolUpvsaptymazDWKxmBHTlWXZm405BFZoNpuGgwEmk4mE2SGtVivii4f1AO7J3ZopkQCQj7Ar1FeRChCJRJzVapX6DKNIfSc1Ax+wtQWQ55h6bH8FWDfYV4fO3wlwDr0C/BcADYiTPCxHqIEA2QsCZAkAKnRGkMbKN/sTX5YHPQ1e7SkAAAAASUVORK5CYII=';
|
||||
img.height = 20;
|
||||
img.width = 20;
|
||||
img.alt = 'Twenty logo';
|
||||
|
||||
// Write universal styles for the button
|
||||
const divStyles = {
|
||||
border: '1px solid black',
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'black',
|
||||
color: 'white',
|
||||
fontWeight: '600',
|
||||
fontSize: '1.5rem',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '5px',
|
||||
justifyContent: 'center',
|
||||
padding: '0 1rem',
|
||||
cursor: 'pointer',
|
||||
height: '50px',
|
||||
width: '50px',
|
||||
position: 'fixed',
|
||||
bottom: '80px',
|
||||
right: '20px',
|
||||
zIndex: '9999999999999999999999999',
|
||||
};
|
||||
|
||||
div.addEventListener('mouseenter', () => {
|
||||
const hoverStyles = {
|
||||
//eslint-disable-next-line @nx/workspace-no-hardcoded-colors
|
||||
backgroundColor: '#5e5e5e',
|
||||
//eslint-disable-next-line @nx/workspace-no-hardcoded-colors
|
||||
borderColor: '#5e5e5e',
|
||||
};
|
||||
Object.assign(div.style, hoverStyles);
|
||||
});
|
||||
|
||||
div.addEventListener('mouseleave', () => {
|
||||
Object.assign(div.style, divStyles);
|
||||
});
|
||||
|
||||
div.onclick = async () => {
|
||||
chrome.runtime.sendMessage({ action: 'openSidepanel' });
|
||||
chrome.storage.local.set({ navigateSidepanel: 'settings' });
|
||||
};
|
||||
|
||||
div.appendChild(img);
|
||||
|
||||
Object.assign(div.style, divStyles);
|
||||
|
||||
document.body.appendChild(div);
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
const changeSidePanelUrl = async (url: string) => {
|
||||
const { tab: activeTab } = await chrome.runtime.sendMessage({
|
||||
action: 'getActiveTab',
|
||||
});
|
||||
if (isDefined(activeTab) && isDefined(url)) {
|
||||
chrome.storage.local.set({ [`sidepanelUrl_${activeTab.id}`]: url });
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'changeSidepanelUrl',
|
||||
message: { url },
|
||||
});
|
||||
if (isDefined(url)) {
|
||||
chrome.storage.local.set({ navigateSidepanel: 'sidepanel' });
|
||||
// we first clear the sidepanelUrl to trigger the onchange listener on sidepanel
|
||||
// which will pass the post meessage to handle internal navigation of iframe
|
||||
chrome.storage.local.set({ sidepanelUrl: '' });
|
||||
chrome.storage.local.set({ sidepanelUrl: url });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -32,7 +32,10 @@ export default defineManifest({
|
||||
content_scripts: [
|
||||
{
|
||||
matches: ['https://www.linkedin.com/*'],
|
||||
js: ['src/contentScript/index.ts'],
|
||||
js: [
|
||||
'src/contentScript/index.ts',
|
||||
'src/contentScript/insertSettingsButton.ts',
|
||||
],
|
||||
run_at: 'document_end',
|
||||
},
|
||||
],
|
||||
|
||||
42
packages/twenty-chrome-extension/src/options/App.tsx
Normal file
42
packages/twenty-chrome-extension/src/options/App.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import Settings from '~/options/Settings';
|
||||
import Sidepanel from '~/options/Sidepanel';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
const App = () => {
|
||||
const [currentScreen, setCurrentScreen] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const setCurrentScreenState = async () => {
|
||||
const store = await chrome.storage.local.get(['navigateSidepanel']);
|
||||
if (isDefined(store.navigateSidepanel)) {
|
||||
setCurrentScreen(store.navigateSidepanel);
|
||||
}
|
||||
};
|
||||
|
||||
setCurrentScreenState();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
chrome.storage.local.onChanged.addListener((updatedStore) => {
|
||||
if (
|
||||
isDefined(updatedStore.navigateSidepanel) &&
|
||||
isDefined(updatedStore.navigateSidepanel.newValue)
|
||||
) {
|
||||
setCurrentScreen(updatedStore.navigateSidepanel.newValue);
|
||||
}
|
||||
});
|
||||
}, [setCurrentScreen]);
|
||||
|
||||
switch (currentScreen) {
|
||||
case 'sidepanel':
|
||||
return <Sidepanel />;
|
||||
case 'settings':
|
||||
return <Settings />;
|
||||
default:
|
||||
return <Settings />;
|
||||
}
|
||||
};
|
||||
|
||||
export default App;
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { MainButton } from '@/ui/input/button/MainButton';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { clearStore } from '~/utils/apolloClient';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
@@ -34,33 +36,47 @@ const StyledActionContainer = styled.div`
|
||||
const Settings = () => {
|
||||
const [serverBaseUrl, setServerBaseUrl] = useState('');
|
||||
const [clientUrl, setClientUrl] = useState('');
|
||||
const [currentClientUrl, setCurrentClientUrl] = useState('');
|
||||
const [currentServerUrl, setCurrentServerUrl] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const getState = async () => {
|
||||
const store = await chrome.storage.local.get();
|
||||
const store = await chrome.storage.local.get([
|
||||
'serverBaseUrl',
|
||||
'clientUrl',
|
||||
]);
|
||||
if (isDefined(store.serverBaseUrl)) {
|
||||
setServerBaseUrl(store.serverBaseUrl);
|
||||
setCurrentServerUrl(store.serverBaseUrl);
|
||||
} else {
|
||||
setServerBaseUrl(import.meta.env.VITE_SERVER_BASE_URL);
|
||||
setCurrentServerUrl(import.meta.env.VITE_SERVER_BASE_URL);
|
||||
}
|
||||
|
||||
if (isDefined(store.clientUrl)) {
|
||||
setClientUrl(store.clientUrl);
|
||||
setCurrentClientUrl(store.clientUrl);
|
||||
} else {
|
||||
setClientUrl(import.meta.env.VITE_FRONT_BASE_URL);
|
||||
setCurrentClientUrl(import.meta.env.VITE_FRONT_BASE_URL);
|
||||
}
|
||||
};
|
||||
void getState();
|
||||
}, []);
|
||||
|
||||
const handleBaseUrlChange = (value: string) => {
|
||||
setServerBaseUrl(value);
|
||||
chrome.storage.local.set({ serverBaseUrl: value });
|
||||
const handleSettingsChange = () => {
|
||||
chrome.storage.local.set({
|
||||
serverBaseUrl,
|
||||
clientUrl,
|
||||
navigateSidepanel: 'sidepanel',
|
||||
});
|
||||
clearStore();
|
||||
};
|
||||
|
||||
const handleClientUrlChange = (value: string) => {
|
||||
setClientUrl(value);
|
||||
chrome.storage.local.set({ clientUrl: value });
|
||||
const handleCloseSettings = () => {
|
||||
chrome.storage.local.set({
|
||||
navigateSidepanel: 'sidepanel',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -71,17 +87,33 @@ const Settings = () => {
|
||||
<TextInput
|
||||
label="Client URL"
|
||||
value={clientUrl}
|
||||
onChange={handleClientUrlChange}
|
||||
onChange={setClientUrl}
|
||||
placeholder="My client URL"
|
||||
fullWidth
|
||||
/>
|
||||
<TextInput
|
||||
label="Server URL"
|
||||
value={serverBaseUrl}
|
||||
onChange={handleBaseUrlChange}
|
||||
onChange={setServerBaseUrl}
|
||||
placeholder="My server URL"
|
||||
fullWidth
|
||||
/>
|
||||
<MainButton
|
||||
title="Done"
|
||||
disabled={
|
||||
currentClientUrl === clientUrl &&
|
||||
currentServerUrl === serverBaseUrl
|
||||
}
|
||||
variant="primary"
|
||||
onClick={handleSettingsChange}
|
||||
fullWidth
|
||||
/>
|
||||
<MainButton
|
||||
title="Close"
|
||||
variant="secondary"
|
||||
onClick={handleCloseSettings}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledActionContainer>
|
||||
</StyledContainer>
|
||||
</StyledWrapper>
|
||||
|
||||
@@ -46,44 +46,103 @@ const Sidepanel = () => {
|
||||
const iframeRef = useRef<HTMLIFrameElement>(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',
|
||||
});
|
||||
const store = await chrome.storage.local.get([
|
||||
'isAuthenticated',
|
||||
'sidepanelUrl',
|
||||
'clientUrl',
|
||||
'accessToken',
|
||||
'refreshToken',
|
||||
]);
|
||||
|
||||
if (
|
||||
isDefined(activeTab) &&
|
||||
isDefined(store[`sidepanelUrl_${activeTab.id}`])
|
||||
store.isAuthenticated === true &&
|
||||
isDefined(store.accessToken) &&
|
||||
isDefined(store.refreshToken) &&
|
||||
new Date(store.accessToken.expiresAt).getTime() >= Date.now()
|
||||
) {
|
||||
const url = store[`sidepanelUrl_${activeTab.id}`];
|
||||
setClientUrl(url);
|
||||
} else if (isDefined(store.clientUrl)) {
|
||||
setClientUrl(store.clientUrl);
|
||||
setIsAuthenticated(true);
|
||||
if (isDefined(store.sidepanelUrl)) {
|
||||
if (isDefined(store.clientUrl)) {
|
||||
setClientUrl(`${store.clientUrl}${store.sidepanelUrl}`);
|
||||
} else {
|
||||
setClientUrl(
|
||||
`${import.meta.env.VITE_FRONT_BASE_URL}${store.sidepanelUrl}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
chrome.storage.local.set({ isAuthenticated: false });
|
||||
if (isDefined(store.clientUrl)) {
|
||||
setClientUrl(store.clientUrl);
|
||||
}
|
||||
}
|
||||
}, [setClientUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
const initState = async () => {
|
||||
const store = await chrome.storage.local.get();
|
||||
if (isDefined(store.isAuthenticated)) setIsAuthenticated(true);
|
||||
void setIframeState();
|
||||
};
|
||||
void initState();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
void setIframeState();
|
||||
}, [setIframeState]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('message', async (event) => {
|
||||
const store = await chrome.storage.local.get([
|
||||
'clientUrl',
|
||||
'accessToken',
|
||||
'refreshToken',
|
||||
]);
|
||||
const clientUrl = isDefined(store.clientUrl)
|
||||
? store.clientUrl
|
||||
: import.meta.env.VITE_FRONT_BASE_URL;
|
||||
|
||||
if (
|
||||
isDefined(store.accessToken) &&
|
||||
isDefined(store.refreshToken) &&
|
||||
event.origin === clientUrl &&
|
||||
event.data === 'loaded'
|
||||
) {
|
||||
event.source?.postMessage(
|
||||
{
|
||||
type: 'tokens',
|
||||
value: {
|
||||
accessToken: {
|
||||
token: store.accessToken.token,
|
||||
expiresAt: store.accessToken.expiresAt,
|
||||
},
|
||||
refreshToken: {
|
||||
token: store.refreshToken.token,
|
||||
expiresAt: store.refreshToken.expiresAt,
|
||||
},
|
||||
},
|
||||
},
|
||||
clientUrl,
|
||||
);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
void setIframeState();
|
||||
}, [setIframeState, clientUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
chrome.storage.local.onChanged.addListener((store) => {
|
||||
if (isDefined(store.isAuthenticated)) {
|
||||
if (store.isAuthenticated.newValue === true) {
|
||||
chrome.storage.local.onChanged.addListener(async (updatedStore) => {
|
||||
if (isDefined(updatedStore.isAuthenticated)) {
|
||||
if (updatedStore.isAuthenticated.newValue === true) {
|
||||
setIframeState();
|
||||
}
|
||||
}
|
||||
|
||||
if (isDefined(updatedStore.sidepanelUrl)) {
|
||||
if (isDefined(updatedStore.sidepanelUrl.newValue)) {
|
||||
const store = await chrome.storage.local.get(['clientUrl']);
|
||||
const clientUrl = isDefined(store.clientUrl)
|
||||
? store.clientUrl
|
||||
: import.meta.env.VITE_FRONT_BASE_URL;
|
||||
|
||||
iframeRef.current?.contentWindow?.postMessage(
|
||||
{
|
||||
type: 'navigate',
|
||||
value: updatedStore.sidepanelUrl.newValue,
|
||||
},
|
||||
clientUrl,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, [setIframeState]);
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ import ReactDOM from 'react-dom/client';
|
||||
|
||||
import { AppThemeProvider } from '@/ui/theme/components/AppThemeProvider';
|
||||
import { ThemeType } from '@/ui/theme/constants/ThemeLight';
|
||||
import Sidepanel from '~/options/Sidepanel';
|
||||
import App from '~/options/App';
|
||||
|
||||
import '~/index.css';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('app') as HTMLElement).render(
|
||||
<AppThemeProvider>
|
||||
<React.StrictMode>
|
||||
<Sidepanel />
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
</AppThemeProvider>,
|
||||
);
|
||||
|
||||
@@ -1,34 +1,19 @@
|
||||
import {
|
||||
ApolloClient,
|
||||
from,
|
||||
fromPromise,
|
||||
HttpLink,
|
||||
InMemoryCache,
|
||||
} from '@apollo/client';
|
||||
import { ApolloClient, from, HttpLink, InMemoryCache } from '@apollo/client';
|
||||
import { setContext } from '@apollo/client/link/context';
|
||||
import { onError } from '@apollo/client/link/error';
|
||||
|
||||
import { renewToken } from '~/db/token.db';
|
||||
import { Tokens } from '~/db/types/auth.types';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
const clearStore = () => {
|
||||
chrome.storage.local.remove(['loginToken', 'accessToken', 'refreshToken']);
|
||||
export const clearStore = () => {
|
||||
chrome.storage.local.remove([
|
||||
'loginToken',
|
||||
'accessToken',
|
||||
'refreshToken',
|
||||
'sidepanelUrl',
|
||||
]);
|
||||
chrome.storage.local.set({ isAuthenticated: false });
|
||||
};
|
||||
|
||||
const setStore = (tokens: Tokens) => {
|
||||
if (isDefined(tokens.loginToken)) {
|
||||
chrome.storage.local.set({
|
||||
loginToken: tokens.loginToken,
|
||||
});
|
||||
}
|
||||
chrome.storage.local.set({
|
||||
accessToken: tokens.accessToken,
|
||||
refreshToken: tokens.refreshToken,
|
||||
});
|
||||
};
|
||||
|
||||
export const getServerUrl = async () => {
|
||||
const store = await chrome.storage.local.get();
|
||||
const serverUrl = `${
|
||||
@@ -46,8 +31,6 @@ const getAuthToken = async () => {
|
||||
};
|
||||
|
||||
const getApolloClient = async () => {
|
||||
const store = await chrome.storage.local.get();
|
||||
|
||||
const authLink = setContext(async (_, { headers }) => {
|
||||
const token = await getAuthToken();
|
||||
return {
|
||||
@@ -57,57 +40,37 @@ const getApolloClient = async () => {
|
||||
},
|
||||
};
|
||||
});
|
||||
const errorLink = onError(
|
||||
({ graphQLErrors, networkError, forward, operation }) => {
|
||||
if (isDefined(graphQLErrors)) {
|
||||
for (const graphQLError of graphQLErrors) {
|
||||
if (graphQLError.message === 'Unauthorized') {
|
||||
return fromPromise(
|
||||
renewToken(store.refreshToken.token)
|
||||
.then((response) => {
|
||||
if (isDefined(response)) {
|
||||
setStore(response.renewToken.tokens);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
clearStore();
|
||||
}),
|
||||
).flatMap(() => forward(operation));
|
||||
}
|
||||
switch (graphQLError?.extensions?.code) {
|
||||
case 'UNAUTHENTICATED': {
|
||||
return fromPromise(
|
||||
renewToken(store.refreshToken.token)
|
||||
.then((response) => {
|
||||
if (isDefined(response)) {
|
||||
setStore(response.renewToken.tokens);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
clearStore();
|
||||
}),
|
||||
).flatMap(() => forward(operation));
|
||||
}
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
`[GraphQL error]: Message: ${graphQLError.message}, Location: ${
|
||||
graphQLError.locations
|
||||
? JSON.stringify(graphQLError.locations)
|
||||
: graphQLError.locations
|
||||
}, Path: ${graphQLError.path}`,
|
||||
);
|
||||
break;
|
||||
const errorLink = onError(({ graphQLErrors, networkError }) => {
|
||||
if (isDefined(graphQLErrors)) {
|
||||
for (const graphQLError of graphQLErrors) {
|
||||
if (graphQLError.message === 'Unauthorized') {
|
||||
clearStore();
|
||||
return;
|
||||
}
|
||||
switch (graphQLError?.extensions?.code) {
|
||||
case 'UNAUTHENTICATED': {
|
||||
clearStore();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
`[GraphQL error]: Message: ${graphQLError.message}, Location: ${
|
||||
graphQLError.locations
|
||||
? JSON.stringify(graphQLError.locations)
|
||||
: graphQLError.locations
|
||||
}, Path: ${graphQLError.path}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isDefined(networkError)) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`[Network error]: ${networkError}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
if (isDefined(networkError)) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`[Network error]: ${networkError}`);
|
||||
}
|
||||
});
|
||||
|
||||
const httpLink = new HttpLink({
|
||||
uri: await getServerUrl(),
|
||||
|
||||
Reference in New Issue
Block a user