mirror of
				https://github.com/lingble/twenty.git
				synced 2025-10-31 20:57:55 +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:
		| @@ -75,7 +75,6 @@ | ||||
|     "class-transformer": "^0.5.1", | ||||
|     "clsx": "^2.1.1", | ||||
|     "cross-env": "^7.0.3", | ||||
|     "crypto-js": "^4.2.0", | ||||
|     "danger-plugin-todos": "^1.3.1", | ||||
|     "dataloader": "^2.2.2", | ||||
|     "date-fns": "^2.30.0", | ||||
| @@ -241,8 +240,6 @@ | ||||
|     "@types/better-sqlite3": "^7.6.8", | ||||
|     "@types/bytes": "^3.1.1", | ||||
|     "@types/chrome": "^0.0.267", | ||||
|     "@types/crypto-js": "^4.2.2", | ||||
|     "@types/dagre": "^0.7.52", | ||||
|     "@types/deep-equal": "^1.0.1", | ||||
|     "@types/express": "^4.17.13", | ||||
|     "@types/graphql-fields": "^1.3.6", | ||||
|   | ||||
| @@ -11,10 +11,11 @@ | ||||
|       } | ||||
|     }, | ||||
|     "start": { | ||||
|       "executor": "@nx/vite:dev-server", | ||||
|       "executor": "nx:run-commands", | ||||
|       "dependsOn": ["build"], | ||||
|       "options": { | ||||
|         "buildTarget": "twenty-chrome-extension:build", | ||||
|         "hmr": true | ||||
|         "cwd": "packages/twenty-chrome-extension", | ||||
|         "command": "VITE_MODE=development vite" | ||||
|       } | ||||
|     }, | ||||
|     "preview": { | ||||
|   | ||||
| @@ -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,8 +71,16 @@ const setTokenStateFromCookie = (cookie: string) => { | ||||
|  | ||||
| chrome.cookies.onChanged.addListener(async ({ cookie }) => { | ||||
|   if (cookie.name === 'tokenPair') { | ||||
|     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); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| }); | ||||
|  | ||||
| // This will only run the very first time the extension loads, after we have stored the | ||||
|   | ||||
| @@ -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 () => { | ||||
|  | ||||
| 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)) { | ||||
|       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 | ||||
|   }, [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,36 +40,17 @@ const getApolloClient = async () => { | ||||
|       }, | ||||
|     }; | ||||
|   }); | ||||
|   const errorLink = onError( | ||||
|     ({ graphQLErrors, networkError, forward, operation }) => { | ||||
|   const errorLink = onError(({ graphQLErrors, networkError }) => { | ||||
|     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)); | ||||
|           return; | ||||
|         } | ||||
|         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)); | ||||
|             break; | ||||
|           } | ||||
|           default: | ||||
|             // eslint-disable-next-line no-console | ||||
| @@ -106,8 +70,7 @@ const getApolloClient = async () => { | ||||
|       // eslint-disable-next-line no-console | ||||
|       console.error(`[Network error]: ${networkError}`); | ||||
|     } | ||||
|     }, | ||||
|   ); | ||||
|   }); | ||||
|  | ||||
|   const httpLink = new HttpLink({ | ||||
|     uri: await getServerUrl(), | ||||
|   | ||||
| @@ -13,6 +13,8 @@ import { useRecoilValue } from 'recoil'; | ||||
|  | ||||
| import { ApolloProvider } from '@/apollo/components/ApolloProvider'; | ||||
| import { VerifyEffect } from '@/auth/components/VerifyEffect'; | ||||
| import { ChromeExtensionSidecarEffect } from '@/chrome-extension-sidecar/components/ChromeExtensionSidecarEffect'; | ||||
| import { ChromeExtensionSidecarProvider } from '@/chrome-extension-sidecar/components/ChromeExtensionSidecarProvider'; | ||||
| import { ClientConfigProvider } from '@/client-config/components/ClientConfigProvider'; | ||||
| import { ClientConfigProviderEffect } from '@/client-config/components/ClientConfigProviderEffect'; | ||||
| import { billingState } from '@/client-config/states/billingState'; | ||||
| @@ -85,9 +87,12 @@ const ProvidersThatNeedRouterContext = () => { | ||||
|   const pageTitle = getPageTitleFromPath(pathname); | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <ApolloProvider> | ||||
|         <ClientConfigProviderEffect /> | ||||
|         <ClientConfigProvider> | ||||
|           <ChromeExtensionSidecarEffect /> | ||||
|           <ChromeExtensionSidecarProvider> | ||||
|             <UserProviderEffect /> | ||||
|             <UserProvider> | ||||
|               <ApolloMetadataClientProvider> | ||||
| @@ -113,8 +118,10 @@ const ProvidersThatNeedRouterContext = () => { | ||||
|                 </ObjectMetadataItemsProvider> | ||||
|               </ApolloMetadataClientProvider> | ||||
|             </UserProvider> | ||||
|           </ChromeExtensionSidecarProvider> | ||||
|         </ClientConfigProvider> | ||||
|       </ApolloProvider> | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -133,6 +133,7 @@ export type ClientConfig = { | ||||
|   authProviders: AuthProviders; | ||||
|   billing: Billing; | ||||
|   captcha: Captcha; | ||||
|   chromeExtensionId?: Maybe<Scalars['String']>; | ||||
|   debugMode: Scalars['Boolean']; | ||||
|   sentry: Sentry; | ||||
|   signInPrefilled: Scalars['Boolean']; | ||||
| @@ -1186,7 +1187,7 @@ export type UpdateBillingSubscriptionMutation = { __typename?: 'Mutation', updat | ||||
| export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>; | ||||
|  | ||||
|  | ||||
| export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, billingFreeTrialDurationInDays?: number | null }, telemetry: { __typename?: 'Telemetry', enabled: boolean, anonymizationEnabled: boolean }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null } } }; | ||||
| export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, chromeExtensionId?: string | null, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, billingFreeTrialDurationInDays?: number | null }, telemetry: { __typename?: 'Telemetry', enabled: boolean, anonymizationEnabled: boolean }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null } } }; | ||||
|  | ||||
| export type UserQueryFragmentFragment = { __typename?: 'User', id: any, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: any, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, defaultWorkspace: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, subscriptionStatus: string, activationStatus: string, currentCacheVersion?: string | null, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: any, key: string, value: boolean, workspaceId: string }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: any, status: string, interval?: string | null } | null }, workspaces: Array<{ __typename?: 'UserWorkspace', workspace?: { __typename?: 'Workspace', id: any, logo?: string | null, displayName?: string | null, domainName?: string | null } | null }> }; | ||||
|  | ||||
| @@ -2307,6 +2308,7 @@ export const GetClientConfigDocument = gql` | ||||
|       provider | ||||
|       siteKey | ||||
|     } | ||||
|     chromeExtensionId | ||||
|   } | ||||
| } | ||||
|     `; | ||||
|   | ||||
| @@ -0,0 +1,58 @@ | ||||
| import { useEffect } from 'react'; | ||||
| import { useNavigate } from 'react-router-dom'; | ||||
| import { useRecoilValue, useSetRecoilState } from 'recoil'; | ||||
|  | ||||
| import { tokenPairState } from '@/auth/states/tokenPairState'; | ||||
| import { isLoadingTokensFromExtensionState } from '@/chrome-extension-sidecar/states/isLoadingTokensFromExtensionState'; | ||||
| import { chromeExtensionIdState } from '@/client-config/states/chromeExtensionIdState'; | ||||
| import { isDefined } from '~/utils/isDefined'; | ||||
| import { isInFrame } from '~/utils/isInIframe'; | ||||
|  | ||||
| export const ChromeExtensionSidecarEffect = () => { | ||||
|   const navigate = useNavigate(); | ||||
|   const setTokenPair = useSetRecoilState(tokenPairState); | ||||
|   const chromeExtensionId = useRecoilValue(chromeExtensionIdState); | ||||
|   const setIsLoadingTokensFromExtension = useSetRecoilState( | ||||
|     isLoadingTokensFromExtensionState, | ||||
|   ); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (isInFrame() && isDefined(chromeExtensionId)) { | ||||
|       window.parent.postMessage( | ||||
|         'loaded', | ||||
|         `chrome-extension://${chromeExtensionId}`, | ||||
|       ); | ||||
|  | ||||
|       const handleWindowEvents = (event: MessageEvent<any>) => { | ||||
|         if (event.origin === `chrome-extension://${chromeExtensionId}`) { | ||||
|           switch (event.data.type) { | ||||
|             case 'tokens': { | ||||
|               setTokenPair(event.data.value); | ||||
|               setIsLoadingTokensFromExtension(true); | ||||
|               break; | ||||
|             } | ||||
|             case 'navigate': | ||||
|               navigate(event.data.value); | ||||
|               break; | ||||
|             default: | ||||
|               break; | ||||
|           } | ||||
|         } else { | ||||
|           setIsLoadingTokensFromExtension(false); | ||||
|           return; | ||||
|         } | ||||
|       }; | ||||
|       window.addEventListener('message', handleWindowEvents); | ||||
|       return () => { | ||||
|         window.removeEventListener('message', handleWindowEvents); | ||||
|       }; | ||||
|     } | ||||
|   }, [ | ||||
|     chromeExtensionId, | ||||
|     setIsLoadingTokensFromExtension, | ||||
|     setTokenPair, | ||||
|     navigate, | ||||
|   ]); | ||||
|  | ||||
|   return <></>; | ||||
| }; | ||||
| @@ -0,0 +1,56 @@ | ||||
| import styled from '@emotion/styled'; | ||||
| import { useRecoilValue } from 'recoil'; | ||||
|  | ||||
| import { isLoadingTokensFromExtensionState } from '@/chrome-extension-sidecar/states/isLoadingTokensFromExtensionState'; | ||||
| import { chromeExtensionIdState } from '@/client-config/states/chromeExtensionIdState'; | ||||
| import { isDefined } from '~/utils/isDefined'; | ||||
| import { isInFrame } from '~/utils/isInIframe'; | ||||
|  | ||||
| const StyledContainer = styled.div` | ||||
|   align-items: center; | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   height: 100vh; | ||||
|   justify-content: center; | ||||
| `; | ||||
|  | ||||
| const AppInaccessible = ({ message }: { message: string }) => { | ||||
|   return ( | ||||
|     <StyledContainer> | ||||
|       <img | ||||
|         src="/images/integrations/twenty-logo.svg" | ||||
|         alt="twenty-icon" | ||||
|         height={40} | ||||
|         width={40} | ||||
|       /> | ||||
|       <h3>{message}</h3> | ||||
|     </StyledContainer> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export const ChromeExtensionSidecarProvider: React.FC< | ||||
|   React.PropsWithChildren | ||||
| > = ({ children }) => { | ||||
|   const isLoadingTokensFromExtension = useRecoilValue( | ||||
|     isLoadingTokensFromExtensionState, | ||||
|   ); | ||||
|   const chromeExtensionId = useRecoilValue(chromeExtensionIdState); | ||||
|  | ||||
|   if (!isInFrame()) return <>{children}</>; | ||||
|  | ||||
|   if (!isDefined(chromeExtensionId)) | ||||
|     return ( | ||||
|       <AppInaccessible message={`Twenty is not accessible inside an iframe.`} /> | ||||
|     ); | ||||
|  | ||||
|   if (isDefined(isLoadingTokensFromExtension) && !isLoadingTokensFromExtension) | ||||
|     return ( | ||||
|       <AppInaccessible | ||||
|         message={`Unauthorized access from iframe origin. If you're trying to access from chrome extension, | ||||
|       please check your chrome extension ID on your server. | ||||
|     `} | ||||
|       /> | ||||
|     ); | ||||
|  | ||||
|   return isLoadingTokensFromExtension && <>{children}</>; | ||||
| }; | ||||
| @@ -0,0 +1,6 @@ | ||||
| import { createState } from 'twenty-ui'; | ||||
|  | ||||
| export const isLoadingTokensFromExtensionState = createState<boolean | null>({ | ||||
|   key: 'isLoadingTokensFromExtensionState', | ||||
|   defaultValue: null, | ||||
| }); | ||||
| @@ -4,6 +4,7 @@ import { useRecoilState, useSetRecoilState } from 'recoil'; | ||||
| import { authProvidersState } from '@/client-config/states/authProvidersState'; | ||||
| import { billingState } from '@/client-config/states/billingState'; | ||||
| import { captchaProviderState } from '@/client-config/states/captchaProviderState'; | ||||
| import { chromeExtensionIdState } from '@/client-config/states/chromeExtensionIdState'; | ||||
| import { isClientConfigLoadedState } from '@/client-config/states/isClientConfigLoadedState'; | ||||
| import { isDebugModeState } from '@/client-config/states/isDebugModeState'; | ||||
| import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState'; | ||||
| @@ -32,6 +33,8 @@ export const ClientConfigProviderEffect = () => { | ||||
|  | ||||
|   const setCaptchaProvider = useSetRecoilState(captchaProviderState); | ||||
|  | ||||
|   const setChromeExtensionId = useSetRecoilState(chromeExtensionIdState); | ||||
|  | ||||
|   const { data, loading } = useGetClientConfigQuery({ | ||||
|     skip: isClientConfigLoaded, | ||||
|   }); | ||||
| @@ -63,6 +66,8 @@ export const ClientConfigProviderEffect = () => { | ||||
|         provider: data?.clientConfig?.captcha?.provider, | ||||
|         siteKey: data?.clientConfig?.captcha?.siteKey, | ||||
|       }); | ||||
|  | ||||
|       setChromeExtensionId(data?.clientConfig?.chromeExtensionId); | ||||
|     } | ||||
|   }, [ | ||||
|     data, | ||||
| @@ -77,6 +82,7 @@ export const ClientConfigProviderEffect = () => { | ||||
|     loading, | ||||
|     setIsClientConfigLoaded, | ||||
|     setCaptchaProvider, | ||||
|     setChromeExtensionId, | ||||
|   ]); | ||||
|  | ||||
|   return <></>; | ||||
|   | ||||
| @@ -33,6 +33,7 @@ export const GET_CLIENT_CONFIG = gql` | ||||
|         provider | ||||
|         siteKey | ||||
|       } | ||||
|       chromeExtensionId | ||||
|     } | ||||
|   } | ||||
| `; | ||||
|   | ||||
| @@ -0,0 +1,6 @@ | ||||
| import { createState } from 'twenty-ui'; | ||||
|  | ||||
| export const chromeExtensionIdState = createState<string | null | undefined>({ | ||||
|   key: 'chromeExtensionIdState', | ||||
|   defaultValue: null, | ||||
| }); | ||||
							
								
								
									
										7
									
								
								packages/twenty-front/src/utils/isInIframe.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								packages/twenty-front/src/utils/isInIframe.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| export const isInFrame = () => { | ||||
|   try { | ||||
|     return window.self !== window.top; | ||||
|   } catch (e) { | ||||
|     return true; | ||||
|   } | ||||
| }; | ||||
| @@ -71,5 +71,5 @@ SIGN_IN_PREFILLED=true | ||||
| # API_RATE_LIMITING_TTL= | ||||
| # API_RATE_LIMITING_LIMIT= | ||||
| # MUTATION_MAXIMUM_RECORD_AFFECTED=100 | ||||
| # CHROME_EXTENSION_REDIRECT_URL=https://bggmipldbceihilonnbpgoeclgbkblkp.chromiumapp.org | ||||
| # CHROME_EXTENSION_ID=bggmipldbceihilonnbpgoeclgbkblkp | ||||
| # PG_SSL_ALLOW_SELF_SIGNED=true | ||||
|   | ||||
| @@ -203,7 +203,9 @@ export class AuthService { | ||||
|           this.environmentService.get('NODE_ENV') === | ||||
|           NodeEnvironment.development | ||||
|             ? authorizeAppInput.redirectUrl | ||||
|             : `${this.environmentService.get('CHROME_EXTENSION_REDIRECT_URL')}`, | ||||
|             : `https://${this.environmentService.get( | ||||
|                 'CHROME_EXTENSION_ID', | ||||
|               )}.chromiumapp.org/`, | ||||
|       }, | ||||
|     ]; | ||||
|  | ||||
|   | ||||
| @@ -96,4 +96,7 @@ export class ClientConfig { | ||||
|  | ||||
|   @Field(() => Captcha) | ||||
|   captcha: Captcha; | ||||
|  | ||||
|   @Field(() => String, { nullable: true }) | ||||
|   chromeExtensionId: string | undefined; | ||||
| } | ||||
|   | ||||
| @@ -48,6 +48,7 @@ export class ClientConfigResolver { | ||||
|         provider: this.environmentService.get('CAPTCHA_DRIVER'), | ||||
|         siteKey: this.environmentService.get('CAPTCHA_SITE_KEY'), | ||||
|       }, | ||||
|       chromeExtensionId: this.environmentService.get('CHROME_EXTENSION_ID'), | ||||
|     }; | ||||
|  | ||||
|     return Promise.resolve(clientConfig); | ||||
|   | ||||
| @@ -380,7 +380,7 @@ export class EnvironmentVariables { | ||||
|  | ||||
|   AUTH_GOOGLE_APIS_CALLBACK_URL: string; | ||||
|  | ||||
|   CHROME_EXTENSION_REDIRECT_URL: string; | ||||
|   CHROME_EXTENSION_ID: string; | ||||
| } | ||||
|  | ||||
| export const validate = ( | ||||
|   | ||||
							
								
								
									
										24
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								yarn.lock
									
									
									
									
									
								
							| @@ -16113,13 +16113,6 @@ __metadata: | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@types/crypto-js@npm:^4.2.2": | ||||
|   version: 4.2.2 | ||||
|   resolution: "@types/crypto-js@npm:4.2.2" | ||||
|   checksum: 760a2078f36f2a3a1089ef367b0d13229876adcf4bcd6e8824d00d9e9bfad8118dc7e6a3cc66322b083535e12be3a29044ccdc9603bfb12519ff61551a3322c6 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@types/d3-array@npm:*": | ||||
|   version: 3.2.1 | ||||
|   resolution: "@types/d3-array@npm:3.2.1" | ||||
| @@ -16452,13 +16445,6 @@ __metadata: | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@types/dagre@npm:^0.7.52": | ||||
|   version: 0.7.52 | ||||
|   resolution: "@types/dagre@npm:0.7.52" | ||||
|   checksum: 0e196a8c17a92765d6e28b10d78d5c1cb1ee540598428cbb61ce3b90e0fedaac2b11f6dbeebf0d2f69d5332d492b12091be5f1e575f538194e20d8887979d006 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "@types/debug@npm:^4.0.0": | ||||
|   version: 4.1.12 | ||||
|   resolution: "@types/debug@npm:4.1.12" | ||||
| @@ -23960,13 +23946,6 @@ __metadata: | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "crypto-js@npm:^4.2.0": | ||||
|   version: 4.2.0 | ||||
|   resolution: "crypto-js@npm:4.2.0" | ||||
|   checksum: 8fbdf9d56f47aea0794ab87b0eb9833baf80b01a7c5c1b0edc7faf25f662fb69ab18dc2199e2afcac54670ff0cd9607a9045a3f7a80336cccd18d77a55b9fdf0 | ||||
|   languageName: node | ||||
|   linkType: hard | ||||
| 
 | ||||
| "crypto-random-string@npm:^2.0.0": | ||||
|   version: 2.0.0 | ||||
|   resolution: "crypto-random-string@npm:2.0.0" | ||||
| @@ -46918,8 +46897,6 @@ __metadata: | ||||
|     "@types/better-sqlite3": "npm:^7.6.8" | ||||
|     "@types/bytes": "npm:^3.1.1" | ||||
|     "@types/chrome": "npm:^0.0.267" | ||||
|     "@types/crypto-js": "npm:^4.2.2" | ||||
|     "@types/dagre": "npm:^0.7.52" | ||||
|     "@types/deep-equal": "npm:^1.0.1" | ||||
|     "@types/dompurify": "npm:^3.0.5" | ||||
|     "@types/express": "npm:^4.17.13" | ||||
| @@ -46980,7 +46957,6 @@ __metadata: | ||||
|     concurrently: "npm:^8.2.2" | ||||
|     cross-env: "npm:^7.0.3" | ||||
|     cross-var: "npm:^1.1.0" | ||||
|     crypto-js: "npm:^4.2.0" | ||||
|     danger: "npm:^11.3.0" | ||||
|     danger-plugin-todos: "npm:^1.3.1" | ||||
|     dataloader: "npm:^2.2.2" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Aditya Pimpalkar
					Aditya Pimpalkar