mirror of
https://github.com/enso-org/enso.git
synced 2025-01-01 02:01:35 +03:00
Add Google Analytics (#8278)
- Add Google Analytics - Add cross-domain linking between website homepage and cloud dashboard - Highlight buttons on authentication flows on hover - Save logged in state as `logged_in` cookie - Remove saved access token from disk when signing out - Support `redirect_to` parameter on `/register` page # Important Notes None
This commit is contained in:
parent
c649ed87af
commit
3046e152dc
@ -158,7 +158,9 @@ export function onOpenUrl(url: URL, window: () => electron.BrowserWindow) {
|
||||
* The credentials file is placed in the user's home directory in the `.enso` subdirectory
|
||||
* in the `credentials` file. */
|
||||
function initSaveAccessTokenListener() {
|
||||
electron.ipcMain.on(ipc.Channel.saveAccessToken, (event, accessToken: string) => {
|
||||
electron.ipcMain.on(ipc.Channel.saveAccessToken, (event, accessToken: string | null) => {
|
||||
event.preventDefault()
|
||||
|
||||
/** Home directory for the credentials file. */
|
||||
const credentialsDirectoryName = `.${common.PRODUCT_NAME.toLowerCase()}`
|
||||
/** File name of the credentials file. */
|
||||
@ -166,22 +168,28 @@ function initSaveAccessTokenListener() {
|
||||
/** System agnostic credentials directory home path. */
|
||||
const credentialsHomePath = path.join(os.homedir(), credentialsDirectoryName)
|
||||
|
||||
fs.mkdir(credentialsHomePath, { recursive: true }, error => {
|
||||
if (error) {
|
||||
logger.error(`Couldn't create ${credentialsDirectoryName} directory.`)
|
||||
} else {
|
||||
fs.writeFile(
|
||||
path.join(credentialsHomePath, credentialsFileName),
|
||||
accessToken,
|
||||
innerError => {
|
||||
if (innerError) {
|
||||
logger.error(`Could not write to ${credentialsFileName} file.`)
|
||||
}
|
||||
}
|
||||
)
|
||||
if (accessToken == null) {
|
||||
try {
|
||||
fs.unlinkSync(path.join(credentialsHomePath, credentialsFileName))
|
||||
} catch {
|
||||
// Ignored, most likely the path does not exist.
|
||||
}
|
||||
})
|
||||
|
||||
event.preventDefault()
|
||||
} else {
|
||||
fs.mkdir(credentialsHomePath, { recursive: true }, error => {
|
||||
if (error) {
|
||||
logger.error(`Couldn't create ${credentialsDirectoryName} directory.`)
|
||||
} else {
|
||||
fs.writeFile(
|
||||
path.join(credentialsHomePath, credentialsFileName),
|
||||
accessToken,
|
||||
innerError => {
|
||||
if (innerError) {
|
||||
logger.error(`Could not write to ${credentialsFileName} file.`)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ const AUTHENTICATION_API = {
|
||||
*
|
||||
* The backend doesn't have access to Electron's `localStorage` so we need to save access token
|
||||
* to a file. Then the token will be used to sign cloud API requests. */
|
||||
saveAccessToken: (accessToken: string) => {
|
||||
saveAccessToken: (accessToken: string | null) => {
|
||||
electron.ipcRenderer.send(ipc.Channel.saveAccessToken, accessToken)
|
||||
},
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
"main": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./src/detect": "./src/detect.ts"
|
||||
"./src/detect": "./src/detect.ts",
|
||||
"./src/gtag": "./src/gtag.ts"
|
||||
}
|
||||
}
|
||||
|
26
app/ide-desktop/lib/common/src/gtag.ts
Normal file
26
app/ide-desktop/lib/common/src/gtag.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/** @file Google Analytics tag. */
|
||||
|
||||
// @ts-expect-error This is explicitly not given types as it is a mistake to acess this
|
||||
// anywhere else.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/strict-boolean-expressions
|
||||
window.dataLayer = window.dataLayer || []
|
||||
|
||||
/** Google Analytics tag function. */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export function gtag(_action: 'config' | 'event' | 'js' | 'set', ..._args: unknown[]) {
|
||||
// @ts-expect-error This is explicitly not given types as it is a mistake to acess this
|
||||
// anywhere else.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||
window.dataLayer.push(arguments)
|
||||
}
|
||||
|
||||
/** Send event to Google Analytics. */
|
||||
export function event(name: string, params?: object) {
|
||||
gtag('event', name, params)
|
||||
}
|
||||
|
||||
gtag('js', new Date())
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
gtag('set', 'linker', { accept_incoming: true })
|
||||
gtag('config', 'G-CLTBJ37MDM')
|
||||
gtag('config', 'G-DH47F649JC')
|
@ -26,9 +26,12 @@ self.addEventListener('install', event => {
|
||||
|
||||
self.addEventListener('fetch', event => {
|
||||
const url = new URL(event.request.url)
|
||||
if (url.hostname === 'localhost' && url.pathname === '/esbuild') {
|
||||
if (
|
||||
(url.hostname === 'localhost' || url.hostname === '127.0.0.1') &&
|
||||
url.pathname === '/esbuild'
|
||||
) {
|
||||
return false
|
||||
} else if (url.hostname === 'localhost') {
|
||||
} else if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
|
||||
const responsePromise = caches
|
||||
.open(constants.CACHE_NAME)
|
||||
.then(cache => cache.match(event.request))
|
||||
|
@ -47,5 +47,15 @@
|
||||
<noscript>
|
||||
This page requires JavaScript to run. Please enable it in your browser.
|
||||
</noscript>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/@twemoji/api@14.1.2/dist/twemoji.min.js"
|
||||
integrity="sha384-D6GSzpW7fMH86ilu73eB95ipkfeXcMPoOGVst/L04yqSSe+RTUY0jXcuEIZk0wrT"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
<!-- Google tag (gtag.js) -->
|
||||
<script
|
||||
async
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-CLTBJ37MDM"
|
||||
></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -10,6 +10,7 @@ import * as common from 'enso-common'
|
||||
import * as contentConfig from 'enso-content-config'
|
||||
import * as dashboard from 'enso-authentication'
|
||||
import * as detect from 'enso-common/src/detect'
|
||||
import * as gtag from 'enso-common/src/gtag'
|
||||
|
||||
import * as remoteLog from './remoteLog'
|
||||
import GLOBAL_CONFIG from '../../../../gui/config.yaml' assert { type: 'yaml' }
|
||||
@ -251,6 +252,7 @@ class Main implements AppRunner {
|
||||
const isInAuthenticationFlow = url.searchParams.has('code') && url.searchParams.has('state')
|
||||
const authenticationUrl = location.href
|
||||
if (isInAuthenticationFlow) {
|
||||
gtag.gtag('event', 'cloud_sign_in_redirect')
|
||||
history.replaceState(null, '', localStorage.getItem(INITIAL_URL_KEY))
|
||||
}
|
||||
const configOptions = contentConfig.OPTIONS.clone()
|
||||
|
@ -24,7 +24,7 @@ self.addEventListener('install', event => {
|
||||
|
||||
self.addEventListener('fetch', event => {
|
||||
const url = new URL(event.request.url)
|
||||
if (url.hostname === 'localhost') {
|
||||
if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
|
||||
return false
|
||||
} else {
|
||||
event.respondWith(
|
||||
|
@ -181,8 +181,8 @@ export class Cognito {
|
||||
}
|
||||
|
||||
/** Save the access token to a file for further reuse. */
|
||||
saveAccessToken(accessToken: string) {
|
||||
this.amplifyConfig.accessTokenSaver?.(accessToken)
|
||||
saveAccessToken(accessToken: string | null) {
|
||||
this.amplifyConfig.saveAccessToken?.(accessToken)
|
||||
}
|
||||
|
||||
/** Return the current {@link UserSession}, or `None` if the user is not logged in.
|
||||
|
@ -95,7 +95,7 @@ export default function ControlledInput(props: ControlledInputProps) {
|
||||
}
|
||||
: onBlur
|
||||
}
|
||||
className="text-sm placeholder-gray-500 pl-10 pr-4 rounded-full border w-full py-2"
|
||||
className="text-sm placeholder-gray-500 hover:bg-gray-100 focus:bg-gray-100 pl-10 pr-4 rounded-full border transition-all duration-300 w-full py-2"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ export default function Link(props: LinkProps) {
|
||||
return (
|
||||
<router.Link
|
||||
to={to}
|
||||
className="flex gap-2 items-center font-bold text-blue-500 hover:text-blue-700 text-xs text-center"
|
||||
className="flex gap-2 items-center font-bold text-blue-500 hover:text-blue-700 focus:text-blue-700 text-xs text-center transition-all duration-300"
|
||||
>
|
||||
<SvgMask src={icon} />
|
||||
{text}
|
||||
|
@ -55,7 +55,7 @@ export default function Login() {
|
||||
event.preventDefault()
|
||||
await signInWithGoogle()
|
||||
}}
|
||||
className="relative rounded-full bg-cloud/10 hover:bg-gray-200 py-2"
|
||||
className="relative rounded-full bg-cloud/10 hover:bg-cloud/20 focus:bg-cloud/20 transition-all duration-300 py-2"
|
||||
>
|
||||
<FontAwesomeIcon icon={fontawesomeIcons.faGoogle} />
|
||||
Sign up or login with Google
|
||||
@ -68,7 +68,7 @@ export default function Login() {
|
||||
event.preventDefault()
|
||||
await signInWithGitHub()
|
||||
}}
|
||||
className="relative rounded-full bg-cloud/10 hover:bg-gray-200 py-2"
|
||||
className="relative rounded-full bg-cloud/10 hover:bg-cloud/20 focus:bg-cloud/20 transition-all duration-300 py-2"
|
||||
>
|
||||
<FontAwesomeIcon icon={fontawesomeIcons.faGithub} />
|
||||
Sign up or login with GitHub
|
||||
@ -117,7 +117,7 @@ export default function Login() {
|
||||
footer={
|
||||
<router.Link
|
||||
to={app.FORGOT_PASSWORD_PATH}
|
||||
className="text-xs text-blue-500 hover:text-blue-700 text-end"
|
||||
className="text-xs text-blue-500 hover:text-blue-700 focus:text-blue-700 transition-all duration-300 text-end"
|
||||
>
|
||||
Forgot Your Password?
|
||||
</router.Link>
|
||||
|
@ -8,6 +8,8 @@ import GoBackIcon from 'enso-assets/go_back.svg'
|
||||
import LockIcon from 'enso-assets/lock.svg'
|
||||
|
||||
import * as authModule from '../providers/auth'
|
||||
import * as localStorageModule from '../../dashboard/localStorage'
|
||||
import * as localStorageProvider from '../../providers/localStorage'
|
||||
import * as string from '../../string'
|
||||
import * as validation from '../../dashboard/validation'
|
||||
|
||||
@ -22,6 +24,7 @@ import SubmitButton from './submitButton'
|
||||
|
||||
const REGISTRATION_QUERY_PARAMS = {
|
||||
organizationId: 'organization_id',
|
||||
redirectTo: 'redirect_to',
|
||||
} as const
|
||||
|
||||
// ====================
|
||||
@ -32,11 +35,20 @@ const REGISTRATION_QUERY_PARAMS = {
|
||||
export default function Registration() {
|
||||
const auth = authModule.useAuth()
|
||||
const location = router.useLocation()
|
||||
const { localStorage } = localStorageProvider.useLocalStorage()
|
||||
const [email, setEmail] = React.useState('')
|
||||
const [password, setPassword] = React.useState('')
|
||||
const [confirmPassword, setConfirmPassword] = React.useState('')
|
||||
const [isSubmitting, setIsSubmitting] = React.useState(false)
|
||||
const { organizationId } = parseUrlSearchParams(location.search)
|
||||
const { organizationId, redirectTo } = parseUrlSearchParams(location.search)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (redirectTo != null) {
|
||||
localStorage.set(localStorageModule.LocalStorageKey.loginRedirect, redirectTo)
|
||||
} else {
|
||||
localStorage.delete(localStorageModule.LocalStorageKey.loginRedirect)
|
||||
}
|
||||
}, [localStorage, redirectTo])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 text-primary text-sm items-center justify-center min-h-screen">
|
||||
@ -98,5 +110,6 @@ export default function Registration() {
|
||||
function parseUrlSearchParams(search: string) {
|
||||
const query = new URLSearchParams(search)
|
||||
const organizationId = query.get(REGISTRATION_QUERY_PARAMS.organizationId)
|
||||
return { organizationId }
|
||||
const redirectTo = query.get(REGISTRATION_QUERY_PARAMS.redirectTo)
|
||||
return { organizationId, redirectTo }
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ export default function SubmitButton(props: SubmitButtonProps) {
|
||||
<button
|
||||
disabled={disabled}
|
||||
type="submit"
|
||||
className="flex gap-2 items-center justify-center focus:outline-none text-white bg-blue-600 hover:bg-blue-700 rounded-full py-2 w-full transition duration-150 ease-in disabled:opacity-50"
|
||||
className="flex gap-2 items-center justify-center focus:outline-none text-white bg-blue-600 hover:bg-blue-700 focus:bg-blue-700 rounded-full py-2 w-full transition-all duration-300 ease-in disabled:opacity-50"
|
||||
>
|
||||
{text}
|
||||
<SvgMask src={icon} />
|
||||
|
@ -71,7 +71,7 @@ export const OAuthRedirect = newtype.newtypeConstructor<OAuthRedirect>()
|
||||
export type OAuthUrlOpener = (url: string, redirectUrl: string) => void
|
||||
/** A function used to save the access token to a credentials file. The token is used by the engine
|
||||
* to issue HTTP requests to the cloud API. */
|
||||
export type AccessTokenSaver = (accessToken: string) => void
|
||||
export type AccessTokenSaver = (accessToken: string | null) => void
|
||||
/** Function used to register a callback. The callback will get called when a deep link is received
|
||||
* by the app. This is only used in the desktop app (i.e., not in the cloud). This is used when the
|
||||
* user is redirected back to the app from the system browser, after completing an OAuth flow. */
|
||||
@ -96,7 +96,6 @@ export const OAUTH_RESPONSE_TYPE = OAuthResponseType('code')
|
||||
// === AmplifyConfig ===
|
||||
// =====================
|
||||
|
||||
// Eslint does not like "etc.".
|
||||
/** Configuration for the AWS Amplify library.
|
||||
*
|
||||
* This details user pools, federated identity providers, etc. that are used to authenticate users.
|
||||
@ -107,7 +106,7 @@ export interface AmplifyConfig {
|
||||
userPoolId: UserPoolId
|
||||
userPoolWebClientId: UserPoolWebClientId
|
||||
urlOpener: OAuthUrlOpener | null
|
||||
accessTokenSaver: AccessTokenSaver | null
|
||||
saveAccessToken: AccessTokenSaver | null
|
||||
domain: OAuthDomain
|
||||
scope: OAuthScope[]
|
||||
redirectSignIn: OAuthRedirect
|
||||
|
@ -9,6 +9,8 @@ import * as toast from 'react-toastify'
|
||||
|
||||
import * as sentry from '@sentry/react'
|
||||
|
||||
import * as gtag from 'enso-common/src/gtag'
|
||||
|
||||
import * as app from '../../components/app'
|
||||
import type * as authServiceModule from '../service'
|
||||
import * as backendModule from '../../dashboard/backend'
|
||||
@ -17,6 +19,7 @@ import * as cognitoModule from '../cognito'
|
||||
import * as errorModule from '../../error'
|
||||
import * as http from '../../http'
|
||||
import * as localBackend from '../../dashboard/localBackend'
|
||||
import * as localStorageModule from '../../dashboard/localStorage'
|
||||
import * as localStorageProvider from '../../providers/localStorage'
|
||||
import * as loggerProvider from '../../providers/logger'
|
||||
import * as remoteBackend from '../../dashboard/remoteBackend'
|
||||
@ -272,6 +275,7 @@ export function AuthProvider(props: AuthProviderProps) {
|
||||
) {
|
||||
setBackendWithoutSavingType(backend)
|
||||
}
|
||||
gtag.event('cloud_open')
|
||||
let organization: backendModule.UserOrOrganization | null
|
||||
let user: backendModule.SimpleUser | null
|
||||
while (true) {
|
||||
@ -279,7 +283,7 @@ export function AuthProvider(props: AuthProviderProps) {
|
||||
organization = await backend.usersMe()
|
||||
try {
|
||||
user =
|
||||
organization != null
|
||||
organization?.isEnabled === true
|
||||
? (await backend.listUsers()).find(
|
||||
listedUser => listedUser.email === organization?.email
|
||||
) ?? null
|
||||
@ -331,11 +335,15 @@ export function AuthProvider(props: AuthProviderProps) {
|
||||
user,
|
||||
}
|
||||
|
||||
/** Save access token so can be reused by Enso backend. */
|
||||
// 34560000 is the recommended max cookie age.
|
||||
const parentDomain = location.hostname.replace(/^[^.]*\./, '')
|
||||
document.cookie = `logged_in=yes;max-age=34560000;domain=${parentDomain};samesite=strict;secure`
|
||||
|
||||
// Save access token so can it be reused by the backend.
|
||||
cognito.saveAccessToken(session.accessToken)
|
||||
|
||||
/** Execute the callback that should inform the Electron app that the user has logged in.
|
||||
* This is done to transition the app from the authentication/dashboard view to the IDE. */
|
||||
// Execute the callback that should inform the Electron app that the user has logged in.
|
||||
// This is done to transition the app from the authentication/dashboard view to the IDE.
|
||||
onAuthenticated(session.accessToken)
|
||||
}
|
||||
|
||||
@ -400,6 +408,7 @@ export function AuthProvider(props: AuthProviderProps) {
|
||||
}
|
||||
|
||||
const signUp = async (username: string, password: string, organizationId: string | null) => {
|
||||
gtag.event('cloud_sign_up')
|
||||
const result = await cognito.signUp(username, password, organizationId)
|
||||
if (result.ok) {
|
||||
toastSuccess(MESSAGES.signUpSuccess)
|
||||
@ -411,6 +420,7 @@ export function AuthProvider(props: AuthProviderProps) {
|
||||
}
|
||||
|
||||
const confirmSignUp = async (email: string, code: string) => {
|
||||
gtag.event('cloud_confirm_sign_up')
|
||||
const result = await cognito.confirmSignUp(email, code)
|
||||
if (result.err) {
|
||||
switch (result.val.kind) {
|
||||
@ -426,6 +436,7 @@ export function AuthProvider(props: AuthProviderProps) {
|
||||
}
|
||||
|
||||
const signInWithPassword = async (email: string, password: string) => {
|
||||
gtag.event('cloud_sign_in', { provider: 'Email' })
|
||||
const result = await cognito.signInWithPassword(email, password)
|
||||
if (result.ok) {
|
||||
toastSuccess(MESSAGES.signInWithPasswordSuccess)
|
||||
@ -443,6 +454,7 @@ export function AuthProvider(props: AuthProviderProps) {
|
||||
toastError('You cannot set your username on the local backend.')
|
||||
return false
|
||||
} else {
|
||||
gtag.event('cloud_user_created')
|
||||
try {
|
||||
const organizationId = await authService.cognito.organizationId()
|
||||
// This should not omit success and error toasts as it is not possible
|
||||
@ -462,7 +474,15 @@ export function AuthProvider(props: AuthProviderProps) {
|
||||
pending: MESSAGES.setUsernameLoading,
|
||||
}
|
||||
)
|
||||
navigate(app.DASHBOARD_PATH)
|
||||
const redirectTo = localStorage.get(
|
||||
localStorageModule.LocalStorageKey.loginRedirect
|
||||
)
|
||||
if (redirectTo != null) {
|
||||
localStorage.delete(localStorageModule.LocalStorageKey.loginRedirect)
|
||||
location.href = redirectTo
|
||||
} else {
|
||||
navigate(app.DASHBOARD_PATH)
|
||||
}
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
@ -503,11 +523,15 @@ export function AuthProvider(props: AuthProviderProps) {
|
||||
}
|
||||
|
||||
const signOut = async () => {
|
||||
const parentDomain = location.hostname.replace(/^[^.]*\./, '')
|
||||
document.cookie = `logged_in=no;max-age=0;domain=${parentDomain}`
|
||||
gtag.event('cloud_sign_out')
|
||||
cognito.saveAccessToken(null)
|
||||
localStorage.clearUserSpecificEntries()
|
||||
deinitializeSession()
|
||||
setInitialized(false)
|
||||
sentry.setUser(null)
|
||||
setUserSession(null)
|
||||
localStorage.clearUserSpecificEntries()
|
||||
// This should not omit success and error toasts as it is not possible
|
||||
// to render this optimistically.
|
||||
await toast.toast.promise(cognito.signOut(), {
|
||||
@ -523,16 +547,20 @@ export function AuthProvider(props: AuthProviderProps) {
|
||||
signUp: withLoadingToast(signUp),
|
||||
confirmSignUp: withLoadingToast(confirmSignUp),
|
||||
setUsername,
|
||||
signInWithGoogle: () =>
|
||||
cognito.signInWithGoogle().then(
|
||||
signInWithGoogle: () => {
|
||||
gtag.event('cloud_sign_in', { provider: 'Google' })
|
||||
return cognito.signInWithGoogle().then(
|
||||
() => true,
|
||||
() => false
|
||||
),
|
||||
signInWithGitHub: () =>
|
||||
cognito.signInWithGitHub().then(
|
||||
)
|
||||
},
|
||||
signInWithGitHub: () => {
|
||||
gtag.event('cloud_sign_in', { provider: 'GitHub' })
|
||||
return cognito.signInWithGitHub().then(
|
||||
() => true,
|
||||
() => false
|
||||
),
|
||||
)
|
||||
},
|
||||
signInWithPassword: withLoadingToast(signInWithPassword),
|
||||
forgotPassword: withLoadingToast(forgotPassword),
|
||||
resetPassword: withLoadingToast(resetPassword),
|
||||
@ -611,10 +639,18 @@ export function ProtectedLayout() {
|
||||
* in the process of registering. */
|
||||
export function SemiProtectedLayout() {
|
||||
const { session } = useAuth()
|
||||
const { localStorage } = localStorageProvider.useLocalStorage()
|
||||
const shouldPreventNavigation = getShouldPreventNavigation()
|
||||
|
||||
if (!shouldPreventNavigation && session?.type === UserSessionType.full) {
|
||||
return <router.Navigate to={app.DASHBOARD_PATH} />
|
||||
const redirectTo = localStorage.get(localStorageModule.LocalStorageKey.loginRedirect)
|
||||
if (redirectTo != null) {
|
||||
localStorage.delete(localStorageModule.LocalStorageKey.loginRedirect)
|
||||
location.href = redirectTo
|
||||
return
|
||||
} else {
|
||||
return <router.Navigate to={app.DASHBOARD_PATH} />
|
||||
}
|
||||
} else {
|
||||
return <router.Outlet context={session} />
|
||||
}
|
||||
@ -628,12 +664,20 @@ export function SemiProtectedLayout() {
|
||||
* not logged in. */
|
||||
export function GuestLayout() {
|
||||
const { session } = useAuth()
|
||||
const { localStorage } = localStorageProvider.useLocalStorage()
|
||||
const shouldPreventNavigation = getShouldPreventNavigation()
|
||||
|
||||
if (!shouldPreventNavigation && session?.type === UserSessionType.partial) {
|
||||
return <router.Navigate to={app.SET_USERNAME_PATH} />
|
||||
} else if (!shouldPreventNavigation && session?.type === UserSessionType.full) {
|
||||
return <router.Navigate to={app.DASHBOARD_PATH} />
|
||||
const redirectTo = localStorage.get(localStorageModule.LocalStorageKey.loginRedirect)
|
||||
if (redirectTo != null) {
|
||||
localStorage.delete(localStorageModule.LocalStorageKey.loginRedirect)
|
||||
location.href = redirectTo
|
||||
return
|
||||
} else {
|
||||
return <router.Navigate to={app.DASHBOARD_PATH} />
|
||||
}
|
||||
} else {
|
||||
return <router.Outlet />
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ function loadAmplifyConfig(
|
||||
/** Load the environment-specific Amplify configuration. */
|
||||
const baseConfig = AMPLIFY_CONFIGS[config.ENVIRONMENT]
|
||||
let urlOpener: ((url: string) => void) | null = null
|
||||
let accessTokenSaver: ((accessToken: string) => void) | null = null
|
||||
let accessTokenSaver: ((accessToken: string | null) => void) | null = null
|
||||
if ('authenticationApi' in window) {
|
||||
/** When running on destop we want to have option to save access token to a file,
|
||||
* so it can be later reuse when issuing requests to Cloud API. */
|
||||
@ -164,7 +164,7 @@ function loadAmplifyConfig(
|
||||
...baseConfig,
|
||||
...platformConfig,
|
||||
urlOpener,
|
||||
accessTokenSaver,
|
||||
saveAccessToken: accessTokenSaver,
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,7 +174,7 @@ function openUrlWithExternalBrowser(url: string) {
|
||||
}
|
||||
|
||||
/** Save the access token to a file. */
|
||||
function saveAccessToken(accessToken: string) {
|
||||
function saveAccessToken(accessToken: string | null) {
|
||||
window.authenticationApi.saveAccessToken(accessToken)
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import DefaultUserIcon from 'enso-assets/default_user.svg'
|
||||
import TriangleDownIcon from 'enso-assets/triangle_down.svg'
|
||||
|
||||
import * as chat from 'enso-chat/chat'
|
||||
import * as gtag from 'enso-common/src/gtag'
|
||||
|
||||
import * as animations from '../../animations'
|
||||
import * as authProvider from '../../authentication/providers/auth'
|
||||
@ -263,8 +264,10 @@ function ChatHeader(props: InternalChatHeaderProps) {
|
||||
setIsThreadListVisible(false)
|
||||
}
|
||||
document.addEventListener('click', onClick)
|
||||
gtag.event('cloud_open_chat')
|
||||
return () => {
|
||||
document.removeEventListener('click', onClick)
|
||||
gtag.event('cloud_close_chat')
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
@ -116,11 +116,14 @@ export default function Drive(props: DriveProps) {
|
||||
|
||||
React.useEffect(() => {
|
||||
void (async () => {
|
||||
if (backend.type !== backendModule.BackendType.local) {
|
||||
if (
|
||||
backend.type !== backendModule.BackendType.local &&
|
||||
organization?.isEnabled === true
|
||||
) {
|
||||
setLabels(await backend.listTags())
|
||||
}
|
||||
})()
|
||||
}, [backend])
|
||||
}, [backend, organization?.isEnabled])
|
||||
|
||||
const doUploadFiles = React.useCallback(
|
||||
(files: File[]) => {
|
||||
|
@ -20,6 +20,7 @@ export enum LocalStorageKey {
|
||||
isTemplatesListOpen = 'is-templates-list-open',
|
||||
projectStartupInfo = 'project-startup-info',
|
||||
driveCategory = 'drive-category',
|
||||
loginRedirect = 'login-redirect',
|
||||
}
|
||||
|
||||
/** The data that can be stored in a {@link LocalStorage}. */
|
||||
@ -30,6 +31,7 @@ interface LocalStorageData {
|
||||
[LocalStorageKey.isTemplatesListOpen]: boolean
|
||||
[LocalStorageKey.projectStartupInfo]: backend.ProjectStartupInfo
|
||||
[LocalStorageKey.driveCategory]: categorySwitcher.Category
|
||||
[LocalStorageKey.loginRedirect]: string
|
||||
}
|
||||
|
||||
/** Whether each {@link LocalStorageKey} is user specific.
|
||||
@ -42,6 +44,7 @@ const IS_USER_SPECIFIC: Record<LocalStorageKey, boolean> = {
|
||||
[LocalStorageKey.isTemplatesListOpen]: false,
|
||||
[LocalStorageKey.projectStartupInfo]: true,
|
||||
[LocalStorageKey.driveCategory]: false,
|
||||
[LocalStorageKey.loginRedirect]: true,
|
||||
}
|
||||
|
||||
/** A LocalStorage data manager. */
|
||||
@ -120,6 +123,12 @@ export class LocalStorage {
|
||||
savedValues[LocalStorageKey.driveCategory]
|
||||
}
|
||||
}
|
||||
if (LocalStorageKey.loginRedirect in savedValues) {
|
||||
const value = savedValues[LocalStorageKey.loginRedirect]
|
||||
if (typeof value === 'string') {
|
||||
this.values[LocalStorageKey.loginRedirect] = value
|
||||
}
|
||||
}
|
||||
if (
|
||||
this.values[LocalStorageKey.projectStartupInfo] == null &&
|
||||
this.values[LocalStorageKey.page] === pageSwitcher.Page.editor
|
||||
|
@ -19,7 +19,10 @@ declare const self: ServiceWorkerGlobalScope
|
||||
|
||||
self.addEventListener('fetch', event => {
|
||||
const url = new URL(event.request.url)
|
||||
if (url.hostname === 'localhost' && url.pathname !== '/esbuild') {
|
||||
if (
|
||||
(url.hostname === 'localhost' || url.hostname === '127.0.0.1') &&
|
||||
url.pathname !== '/esbuild'
|
||||
) {
|
||||
const responsePromise = /\/[^.]+$/.test(new URL(event.request.url).pathname)
|
||||
? fetch('/index.html')
|
||||
: fetch(event.request.url)
|
||||
|
2
app/ide-desktop/lib/types/globals.d.ts
vendored
2
app/ide-desktop/lib/types/globals.d.ts
vendored
@ -50,7 +50,7 @@ interface AuthenticationApi {
|
||||
* via a deep link. See {@link setDeepLinkHandler} for details. */
|
||||
setDeepLinkHandler: (callback: (url: string) => void) => void
|
||||
/** Saves the access token to a file. */
|
||||
saveAccessToken: (access_token: string) => void
|
||||
saveAccessToken: (accessToken: string | null) => void
|
||||
}
|
||||
|
||||
// =====================================
|
||||
|
Loading…
Reference in New Issue
Block a user