Skip to content

Restore session after channel error #1

Description

@colinchadwick

Thanks for the code! I've modified it to restore the session automatically if an error occurs. This usually happens after the computer goes in to standby mode.

import type {
  SupabaseClient,
  RealtimePostgresChangesPayload,
} from '@supabase/supabase-js';

export type Payload = RealtimePostgresChangesPayload<{ [key: string]: any }>;

export interface SupaSnap<T> {
  data: T[];
  payload: Payload;
}

export interface SupaSingleSnap<T> {
  data: T;
  payload: Payload;
}

type SubscribeInput = {
  table: string;
  field?: string;
  value?: string | string[];
  single?: boolean;
  filterName?: FilterNames;
};

// filter types
const _filterNames = ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'in'] as const;
type FilterNames = (typeof _filterNames)[number];

type Single<T> = (
  callback: (snap: SupaSingleSnap<T>) => void
) => () => Promise<'error' | 'ok' | 'timed out'>;

export const realtime = <T>(
  supabase: SupabaseClient,
  { schema = 'public', idField = 'id', limit = 100 } = {}
) => {
  let isSubscribed = false;
  const items: any[] = [];

  const _subscribe = ({
    table,
    field,
    value,
    single = false,
    filterName,
  }: SubscribeInput) => {
    const hasFilter = field && value && filterName;
    const filterString = `${field}=${filterName}.${value}`;
    const filterChannel = hasFilter ? ':' + filterString : '';
    const filter = hasFilter ? filterString : undefined;

    // create the callback function
    return (callback: (snap: SupaSnap<T>) => void) => {
      const initialize = () => {
        let select = supabase.from(table).select('*');
        select = hasFilter
          ? select[filterName](field, value as unknown as unknown[])
          : select;

        // match subscription input, with limit
        select.limit(limit).then(({ data, error }) => {
          if (data) items.push(...data);
          callback({
            data: data ? (single ? data[0] : data) : [],
            payload: {
              schema,
              table,
              errors: error,
            } as unknown as Payload,
          });
        });
      };

      const realtimeEvents = (payload: Payload) => {
        switch (payload.eventType) {
          case 'INSERT':
            items.unshift(payload.new);
            break;
          case 'UPDATE':
            let objIndex1 = items.findIndex(
              (obj) => obj[idField] === payload.new[idField]
            );
            if (objIndex1 !== -1) items[objIndex1] = payload.new;
            break;
          case 'DELETE':
            let objIndex2 = items.findIndex(
              (obj) => obj[idField] === payload.old[idField]
            );
            if (objIndex2 !== -1) items.splice(objIndex2, 1);
            break;
        }

        console.log('items', items);
      };

      const channel = supabase
        .channel(`${schema}:${table}${filterChannel}`)
        .on(
          'postgres_changes',
          { event: '*', schema, table, filter },
          (payload) => {
            realtimeEvents(payload);
            // return ALL data with payload
            return callback({ data: single ? items[0] : items, payload });
          }
        )
        .subscribe((status: any) => {
          if (status === 'CHANNEL_ERROR') {
            supabase.removeChannel(channel).then(() => {
              isSubscribed = false;
            });
          }

          if (status === 'SUBSCRIBED') {
            isSubscribed = true;
            initialize();
          }
        });

      return () => {
        isSubscribed = false;
        supabase.removeChannel(channel);
      };
    };
  };

  return {
    from: (table: string) => {
      return {
        subscribe: (callback: (snap: SupaSnap<T>) => void) => {
          document.addEventListener('visibilitychange', () => {
            if (document.visibilityState === 'visible' && !isSubscribed) {
              _subscribe({ table })(callback);
            }
          });

          return _subscribe({ table })(callback);
        },
      };
    },
  };
};

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions