Skip to content

Access token not refreshed for realtime channels after being offline or in standbyΒ #1732

@colin-chadwick

Description

@colin-chadwick

Bug report

When a user is offline or in standby mode, the access_token is not refreshed as it requires a POST request to auth/v1/token?grant_type=refresh_token (which obviously fails without a connection). When the user comes back online or is active again, Supabase doesn't automatically refetch the access_token and supply it to realtime channels. Instead, the realtime channels error and show the following message, because they try to connect with the old token:

Bildschirmfoto 2024-01-10 um 21 52 05

Even if the access_token is refreshed afterwards, the channels don't seem to pick up the change. The only way to get the channels to work again is by removing and re-initializing them.

To Reproduce

  1. Set the JWT expiry time of the access_token (e. g. 60 = 1 minute) to a low value, to be able to better reproduce the case where a access_token needs a refresh.
  2. Subscribe to a realtime channel.
  3. Turn off WIFI or go into standby mode.
  4. Wait for the current access_token to expire (in this case after 1 minute).
  5. Turn the WIFI on again or disable standby.

The realtime channel tries to reconnect with the old token (because Supabase wasn't able to refetch it without a connection) and errors out. It doesn't pick up any new tokens once Supabase refreshes them again in the usual interval.

Expected behavior

If any realtime channels are still active after re-enabling WIFI or disabling standby, Supabase should refetch the access_token right away and supply it to the channels. This way, they won't error out.

System information

  • OS: macOS
  • Version of supabase-js: 2.39.1

Additional context

I've built a temporary workaround to the fix the issue, maybe it can help:


import { RealtimeChannel, SupabaseClient } from '@supabase/supabase-js'

export const realtime = (client: SupabaseClient) => {
  let channel: RealtimeChannel | null = null
  let subscribed = false
  let reconnect = true
  let reconnecting = false

  const _subscribe = ({ table }) => {

    if (channel) client.removeChannel(channel)

    reconnecting = true

    return (
      callback: (payload: any) => void,
    ) => {
      channel = client
        .channel('realtime')
        .on(
          'postgres_changes',
          {
            event: '*',
            schema: 'public',
            table,
          },
          (payload) =>  {
            callback(payload)
          },
        )
        .subscribe()
        // @ts-expect-error
        .on('phx_reply', {}, async (payload) => {
          if (
            payload.status === 'error' &&
            payload.response.reason.includes('Invalid token')
          ) {
            await client.removeChannel(channel)

            await client.auth.refreshSession()

            subscribed = false

            reconnecting = false
          }
        })
        // @ts-expect-error
        .on('system', {}, async (payload) => {
          if (
            payload.extension === 'postgres_changes' &&
            payload.status === 'ok'
          ) {
            subscribed = true

            reconnecting = false

          }
        })

    }
  }

  return {
    from: (table: string) => {
      return {
        subscribe: (
          callback: (payload: any) => void,
        ) => {
          let timer: NodeJS.Timeout

          const reconnectSubscription = () => {
            clearTimeout(timer)

            timer = setTimeout(() => {
              reconnectSubscription()

              if (
                reconnect &&
                !subscribed &&
                !reconnecting &&
                document.visibilityState === 'visible'
              ) {
                return _subscribe({ table })(callback)
              }
            }, 1000)
          }

          reconnectSubscription()

          return _subscribe({ table })(callback)
        },
        unsubscribe: async () => {
          if (!channel) return

          await client.removeChannel(channel)
          channel = null
          reconnect = false
        },
      }
    },
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingrealtime-jsRelated to the realtime-js library.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions