gotrue.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import { AuthClient, navigatorLock, User } from '@supabase/auth-js'
  2. import { isBrowser } from './helpers'
  3. export const STORAGE_KEY = process.env.NEXT_PUBLIC_STORAGE_KEY || 'briven.dashboard.auth.token'
  4. export const AUTH_DEBUG_KEY =
  5. process.env.NEXT_PUBLIC_AUTH_DEBUG_KEY || 'briven.dashboard.auth.debug'
  6. export const AUTH_DEBUG_PERSISTED_KEY =
  7. process.env.NEXT_PUBLIC_AUTH_DEBUG_PERSISTED_KEY || 'briven.dashboard.auth.debug.persist'
  8. export const AUTH_NAVIGATOR_LOCK_DISABLED_KEY =
  9. process.env.NEXT_PUBLIC_AUTH_NAVIGATOR_LOCK_KEY ||
  10. 'briven.dashboard.auth.navigatorLock.disabled'
  11. /**
  12. * Catches errors thrown when accessing localStorage. Safari with certain
  13. * security settings throws when localStorage is accessed.
  14. */
  15. function safeGetLocalStorage(key: string) {
  16. try {
  17. return globalThis?.localStorage?.getItem(key)
  18. } catch {
  19. return null
  20. }
  21. }
  22. const debug =
  23. process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' && safeGetLocalStorage(AUTH_DEBUG_KEY) === 'true'
  24. const persistedDebug =
  25. process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' &&
  26. safeGetLocalStorage(AUTH_DEBUG_PERSISTED_KEY) === 'true'
  27. const shouldEnableNavigatorLock =
  28. process.env.NEXT_PUBLIC_IS_PLATFORM === 'true' &&
  29. !(safeGetLocalStorage(AUTH_NAVIGATOR_LOCK_DISABLED_KEY) === 'true')
  30. const shouldDetectSessionInUrl = process.env.NEXT_PUBLIC_AUTH_DETECT_SESSION_IN_URL
  31. ? process.env.NEXT_PUBLIC_AUTH_DETECT_SESSION_IN_URL === 'true'
  32. : true
  33. const navigatorLockEnabled = !!(shouldEnableNavigatorLock && globalThis?.navigator?.locks)
  34. if (isBrowser && shouldEnableNavigatorLock && !globalThis?.navigator?.locks) {
  35. console.warn('This browser does not support the Navigator Locks API. Please update it.')
  36. }
  37. const tabId = Math.random().toString(16).substring(2)
  38. let dbHandle = new Promise<IDBDatabase | null>((accept, _) => {
  39. if (!persistedDebug) {
  40. accept(null)
  41. return
  42. }
  43. const request = indexedDB.open('auth-debug-log', 1)
  44. request.onupgradeneeded = (event: any) => {
  45. const db = event?.target?.result
  46. if (!db) {
  47. return
  48. }
  49. db.createObjectStore('events', { autoIncrement: true })
  50. }
  51. request.onsuccess = (event: any) => {
  52. console.log('Opened persisted auth debug log IndexedDB database', tabId)
  53. accept(event.target.result)
  54. }
  55. request.onerror = (event: any) => {
  56. console.error('Failed to open persisted auth debug log IndexedDB database', event)
  57. accept(null)
  58. }
  59. })
  60. const logIndexedDB = (message: string, ...args: any[]) => {
  61. console.log(message, ...args)
  62. const copyArgs = structuredClone(args)
  63. copyArgs.forEach((value) => {
  64. if (typeof value === 'object' && value !== null) {
  65. delete value.user
  66. delete value.access_token
  67. delete value.token_type
  68. delete value.provider_token
  69. }
  70. })
  71. ;(async () => {
  72. try {
  73. const db = await dbHandle
  74. if (!db) {
  75. return
  76. }
  77. const tx = db.transaction(['events'], 'readwrite')
  78. tx.onerror = (event: any) => {
  79. console.error('Failed to write to persisted auth debug log IndexedDB database', event)
  80. dbHandle = Promise.resolve(null)
  81. }
  82. const events = tx.objectStore('events')
  83. events.add({
  84. m: message.replace(/^GoTrueClient@/i, ''),
  85. a: copyArgs,
  86. l: window.location.pathname,
  87. t: tabId,
  88. })
  89. } catch (e: any) {
  90. console.error('Failed to log to persisted auth debug log IndexedDB database', e)
  91. dbHandle = Promise.resolve(null)
  92. }
  93. })()
  94. }
  95. /**
  96. * Reference to a function that captures exceptions for debugging purposes to be sent to Sentry.
  97. */
  98. let captureException: ((e: any) => any) | null = null
  99. export function setCaptureException(fn: typeof captureException) {
  100. captureException = fn
  101. }
  102. async function debuggableNavigatorLock<R>(
  103. name: string,
  104. acquireTimeout: number,
  105. fn: () => Promise<R>
  106. ): Promise<R> {
  107. let stackException: any
  108. try {
  109. throw new Error('Lock is being held for over 10s here')
  110. } catch (e: any) {
  111. stackException = e
  112. }
  113. const debugTimeout = setTimeout(() => {
  114. ;(async () => {
  115. const bc = new BroadcastChannel('who-is-holding-the-lock')
  116. try {
  117. bc.postMessage({})
  118. } finally {
  119. bc.close()
  120. }
  121. console.error(
  122. `Waited for over 10s to acquire an Auth client lock`,
  123. await navigator.locks.query(),
  124. stackException
  125. )
  126. })()
  127. }, 10000)
  128. try {
  129. return await navigatorLock(name, acquireTimeout, async () => {
  130. clearTimeout(debugTimeout)
  131. const bc = new BroadcastChannel('who-is-holding-the-lock')
  132. bc.addEventListener('message', () => {
  133. console.error('Lock is held here', stackException)
  134. if (captureException) {
  135. captureException(stackException)
  136. }
  137. })
  138. try {
  139. return await fn()
  140. } finally {
  141. bc.close()
  142. }
  143. })
  144. } finally {
  145. clearTimeout(debugTimeout)
  146. }
  147. }
  148. export const gotrueClient = new AuthClient({
  149. url: process.env.NEXT_PUBLIC_GOTRUE_URL,
  150. storageKey: STORAGE_KEY,
  151. detectSessionInUrl: shouldDetectSessionInUrl,
  152. debug: debug ? (persistedDebug ? logIndexedDB : true) : false,
  153. lock: navigatorLockEnabled ? debuggableNavigatorLock : undefined,
  154. ...('localStorage' in globalThis
  155. ? { storage: globalThis.localStorage, userStorage: globalThis.localStorage }
  156. : null),
  157. })
  158. export type { User }