Detect Realm Web SDK Watch() Drops

I’m using the watch() method on the web SDK to keep real time updates shown on my application screen. Sometime, for whatever reason, the watch() stops working. It can be after many hours on Chrome or almost immediately on mobile Safari.

Is there a way to detect a watch() that is no longer connected, so that I can have it reconnect upon this dropping?

1 Like

have you found a solution or know why that happens?

Nope. Abandoned the SDK for a different solution.

the watch() Drops happen mostly on Firefox, any solution to this issue?

i recently create a react app by using realm web template app, the web-js one, and have this error show up every few second when fetching data. i think it is related with your watch() drops issue.

Uncaught (in promise) WatchError: rule with id="62ec12a7614......" no longer exists on mongodb service, closing watch stream
    at WatchStream.feedSse (bundle.dom.es.js:1812:1)
    at WatchStream.feedLine (bundle.dom.es.js:1737:1)
    at WatchStream.advanceBufferState (bundle.dom.es.js:1872:1)
    at WatchStream.feedBuffer (bundle.dom.es.js:1716:1)
    at MongoDBCollection.watchImpl (bundle.dom.es.js:2065:1)
    at async watchAction (useWatch.js:45:1)

is there a better place to rise the question to find out answers

I am having the same issue. Are there any solutions for detecting dropped watch()'s?

they created this sandbox

1 Like

i use chrome, it is the same

Li_Li3, thank you, this is heroic.

I fixed my issue by moving the rules from the default to individual collection, i am not sure why, but the watch error disappeared.

1 Like

Made a visual guide to help people with a similar issue, Li_Li3 is right- although I wish I saw their comment before doing my own deductions.

1 Like

Then set granular permissions.

I’m having the same issue using the web sdk.

The issue won’t occur, if I use the example app provided here:
https://codesandbox.io/s/9c8bw

However, if I use that very same example app without any changes but using my own atlas app id, the errors show up again.
So it’s definitely connected to some backend configuration. I just don’t know which one. Will create a new test app and try to apply my settings one by one…

Does anyone have an idea what the problem might be?
It’s not the rules issue as described above for me as I’ve already deleted all default roles for testing.

@Daniel_Weiss difficult to tell, without more information, I too used the code sandbox example and it worked with a test app when I was first problem solving this issue.

I’ll provide some more information on my set-up , my code snippet from my app, and what rules looks like on my end. This was frustrating for me, so I hope this will help you solve it faster.

  1. I’ve deleted the default permissions, and then set manual permissions on each of my collections
  2. I’m on “realm-web”: “^2.0.0”,
  3. My Snippet
export const watchCollection = async () => {
  const processing = app.currentUser
    .mongoClient('mongodb-atlas')
    .db('yourdb')
    .collection('yourcol');
  if (!processing) {
    return;
  }
 for await (const change of changeStream) {
    switch (change.operationType) {
      case 'insert': {
      }
      case 'update': {
        const {fullDocument} = change;
//....do awesome things
break;
      }
       case 'replace': {
       const { documentKey, fullDocument } = change;
         break;
       }
       case 'delete': {
         const { documentKey } = change;
        break;
      }
    }
  }
  return changeStream;
};

/*Then in my react app*/

  useEffect(() => {
    watchCollection();
  }, []);
  1. A screen shot of my rules in the App Services UI

Thank you very much for your help, @Paul_Vu.

I finally figured it out. Having DeviceSync enabled, I assumed that those roles would take precedence and while I haven’t tested for that - I still think they do. However, the error occurs when there are no rules (not DeviceSync permissions but “normal rules”) setup. Once I did that and - importantly - no default rules as mentioned above, it did work.

So, to recap, my setup is as follows:

  • Rules for each collection → no default roles.
  • The very same rules adapted as DeviceSync permissions. Here default roles are ok.
  • DeviceSync is enabled

I connect to the backend via both the Flutter SDK and the Web SDK. It both works now.
Maybe this helps someone else struggling with the same problem.

1 Like

Glad you got it resolved, good to know about the no rules part!

I am using the Realm Web SDK and I have a custom React Hook that does the subscription for the collection watch.

The issue is that the subscription only works once. After the first subscription changes comes through, no other changes work. Is there something I am missing?

import { useDataSalesCashCountList } from "./data-list";
import {
  SelectCrudActionType,
  SelectObjectStatus,
  getCol,
  isDevMode,
  subscriptionMutationFn,
  useDataForUserAuth,
} from "@submodules/booster-pack";
import {
  DataLoadingState,
  SalesCashCount,
  SalesCashCountSchema,
} from "@models";
import { useServiceFnSalesCashCount } from "./data-services";
import { useObserveEffect } from "@legendapp/state/react";
import { observable } from "@legendapp/state";
import { saleDate } from "@pages/sales/make-sale-utils";

const refNameSalesCashCountList = "useDataSalesCashCountList";
const state = useDataSalesCashCountList.items;
const subState = observable({
  shouldSub: null as boolean | null,
  worker: null as Worker | null,
});

export const useDataSalesCashCountSubs = () => {
  const mutationFn = useDataSalesCashCountList.mutationFn;
  const serviceFxns = useServiceFnSalesCashCount;

  const doSubs = async () => {
    try {
      const currentUser = useDataForUserAuth?.currentUser.get();
      const currentTeamMember = useDataForUserAuth?.teamMember.get();
      const isLoggedIn = useDataForUserAuth?.isLoggedIn.get();
      const col = getCol({
        collectionName: SalesCashCountSchema.name,
        currentUser,
      });

      for await (const change of col.watch({
        filter: {
          "fullDocument.companyBranch": currentTeamMember?.companyBranch?._id,
          "fullDocument.teamMemberCashier": currentTeamMember!._id,
          "fullDocument.statusCashier": SelectObjectStatus.ACTIVE,
          "fullDocument.salesDate": saleDate,
        },
      })) {
        let breakAsyncIterator = false;
        switch (change.operationType) {
          case "insert": {
            const { documentKey, fullDocument } = change;

            if (isDevMode) {
              console.log(
                `doSubs on: ${refNameSalesCashCountList} new document: ${documentKey}`,
                fullDocument
              );
            }

            if (serviceFxns.findOneData) {
              const foundItem = await serviceFxns.findOneData({
                id: fullDocument._id,
                userInfo: { currentTeamMember, currentUser, isLoggedIn },
              });

              if (foundItem) {
                await subscriptionMutationFn<SalesCashCount>({
                  newItem: foundItem as SalesCashCount,
                  state,
                  mutationType: SelectCrudActionType.CREATE,
                  mutationFn,
                });
              }
            }

            breakAsyncIterator = true;
            break;
          }
          case "update": {
            const { documentKey, fullDocument } = change;

            if (isDevMode) {
              console.log(
                `doSubs on: ${refNameSalesCashCountList} updated document: ${documentKey}`,
                fullDocument
              );
            }

            if (serviceFxns.findOneData && fullDocument) {
              const foundItem = await serviceFxns.findOneData({
                id: fullDocument._id,
                userInfo: { currentTeamMember, currentUser, isLoggedIn },
              });

              if (foundItem) {
                await subscriptionMutationFn<SalesCashCount>({
                  newItem: foundItem as SalesCashCount,
                  state,
                  mutationType: SelectCrudActionType.UPDATE,
                  mutationFn,
                });
              }
            }

            breakAsyncIterator = true;
            break;
          }
          case "replace": {
            const { documentKey, fullDocument } = change;

            if (isDevMode) {
              console.log(
                `doSubs on: ${refNameSalesCashCountList} replaced document: ${documentKey}`,
                fullDocument
              );
            }

            if (serviceFxns.findOneData && fullDocument) {
              const foundItem = await serviceFxns.findOneData({
                id: fullDocument._id,
                userInfo: { currentTeamMember, currentUser, isLoggedIn },
              });

              if (foundItem) {
                await subscriptionMutationFn<SalesCashCount>({
                  newItem: foundItem as SalesCashCount,
                  state,
                  mutationType: SelectCrudActionType.UPDATE,
                  mutationFn,
                });
              }
            }

            breakAsyncIterator = true;
            break;
          }
          case "delete": {
            const { documentKey } = change;

            if (isDevMode) {
              console.log(
                `doSubs on: ${refNameSalesCashCountList} deleted document: ${documentKey}`
              );
            }

            await subscriptionMutationFn<SalesCashCount>({
              deletedId: documentKey._id,
              state,
              newItem: { _id: documentKey._id } as any,
              mutationType: SelectCrudActionType.DELETE,
              mutationFn,
            });

            breakAsyncIterator = true;
            break;
          }
        }
        if (breakAsyncIterator) break;
      }
    } catch (error) {
      if (isDevMode) {
        console.log(
          `doSubs error on: ${refNameSalesCashCountList} is ${error}`
        );
      }
    }
  };

  useObserveEffect(async () => {
    const currentUser = useDataForUserAuth?.currentUser.get();
    const currentTeamMember = useDataForUserAuth?.teamMember.get();
    const loadingAuthUser = useDataForUserAuth?.loadingAuthUser.get();
    const loadingTeamMember = useDataForUserAuth?.loadingTeamMember.get();

    if (currentUser == null || currentUser == undefined) {
      return;
    }

    if (currentTeamMember == null || currentTeamMember == undefined) {
      return;
    }

    const readyToSub =
      currentUser &&
      currentTeamMember &&
      loadingAuthUser !== DataLoadingState.LOADING &&
      loadingTeamMember !== DataLoadingState.LOADING;

    if (readyToSub == true && subState.shouldSub.get() == null) {
      if (isDevMode) {
        console.log(
          "readyToSub",
          readyToSub,
          "subState",
          subState.shouldSub.get()
        );
      }
      doSubs();
      subState.shouldSub.set(false);
    }
  });
};