Files
supabase/apps/studio/lib/github.ts
Ali Waseem 1c2d28d5b3 chore: wrap local storage into helper methods that are safer (#46628)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

- Noticing our code we have many patterns of calling localstorage and
handling those errors
- We should add those in a single well tested file
- Handle those errors in the singleton which makes it easier for us to
debug customer issues. Logger is outputing local storage warnings for
feature we expose
- Side effect of this is random crashes on studio when local storage
isn't available or handled correctly

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Improved browser storage handling across the app for more reliable
persistence and graceful behavior in restricted or non-browser
environments (settings, previews, charts, tabs, sign-in/session flows,
integrations, and UI state).

* **New Features**
* Introduced a safe storage layer to standardize and harden
local/session persistence.

* **Tests**
  * Added comprehensive tests covering the new safe storage behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-04 07:41:28 -06:00

94 lines
3.2 KiB
TypeScript

import { LOCAL_STORAGE_KEYS, safeLocalStorage } from 'common'
import { makeRandomString } from './helpers'
const GITHUB_INTEGRATION_APP_NAME =
process.env.NEXT_PUBLIC_GITHUB_INTEGRATION_APP_NAME ||
(process.env.NEXT_PUBLIC_IS_NIMBUS !== undefined
? 'supabase-snap'
: process.env.NEXT_PUBLIC_ENVIRONMENT === 'prod'
? `supabase`
: process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging'
? `supabase-staging`
: `supabase-local-testing`)
const GITHUB_INTEGRATION_CLIENT_ID =
process.env.NEXT_PUBLIC_GITHUB_INTEGRATION_CLIENT_ID ||
(process.env.NEXT_PUBLIC_IS_NIMBUS !== undefined
? 'Iv23li2pAiqDGgaSrP8q'
: process.env.NEXT_PUBLIC_ENVIRONMENT === 'prod'
? `Iv1.b91a6d8eaa272168`
: process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging'
? `Iv1.2681ab9a0360d8ad`
: `Iv1.5022a3b44d150fbf`)
const GITHUB_INTEGRATION_AUTHORIZATION_URL = `https://github.com/login/oauth/authorize?client_id=${GITHUB_INTEGRATION_CLIENT_ID}`
export const GITHUB_INTEGRATION_INSTALLATION_URL = `https://github.com/apps/${GITHUB_INTEGRATION_APP_NAME}/installations/new`
export const GITHUB_INTEGRATION_REVOKE_AUTHORIZATION_URL = `https://github.com/settings/connections/applications/${GITHUB_INTEGRATION_CLIENT_ID}`
export function openInstallGitHubIntegrationWindow(
type: 'install' | 'authorize',
closeCallback?: () => void
) {
const w = 600
const h = 800
const dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : window.screenX
const dualScreenTop = window.screenTop !== undefined ? window.screenTop : window.screenY
const width = window.innerWidth
? window.innerWidth
: document.documentElement.clientWidth
? document.documentElement.clientWidth
: screen.width
const height = window.innerHeight
? window.innerHeight
: document.documentElement.clientHeight
? document.documentElement.clientHeight
: screen.height
let windowUrl: string | undefined
if (type === 'install') {
windowUrl = GITHUB_INTEGRATION_INSTALLATION_URL
} else {
const state = makeRandomString(32)
safeLocalStorage.setItem(LOCAL_STORAGE_KEYS.GITHUB_AUTHORIZATION_STATE, state)
windowUrl = `${GITHUB_INTEGRATION_AUTHORIZATION_URL}&state=${state}&prompt=select_account`
}
const systemZoom = width / window.screen.availWidth
const left = (width - w) / 2 / systemZoom + dualScreenLeft
const top = (height - h) / 2 / systemZoom + dualScreenTop
const newWindow = window.open(
windowUrl,
'GitHub',
`scrollbars=yes,resizable=no,status=no,location=no,toolbar=no,menubar=no,
width=${w / systemZoom},
height=${h / systemZoom},
top=${top},
left=${left}
`
)
if (newWindow) {
if (closeCallback) {
// Poll to check if window is closed
const checkClosed = setInterval(() => {
if (newWindow.closed) {
clearInterval(checkClosed)
closeCallback()
}
}, 500) // Check every 500ms
// Add a timeout to prevent infinite polling
setTimeout(() => {
clearInterval(checkClosed)
}, 300000) // 5 minutes timeout
}
newWindow.focus()
}
}
export const getGitHubProfileImgUrl = (username: string) => {
return `https://github.com/${username}.png?size=96`
}