Can't upgrade off `alanning:roles` 3.6

Hi. I’ve got a web app that’s using Meteor 2.x, alanning:roles, and, if it matters, Apollo GraphQL.

That is, .meteor/release says METEOR@2.16, even though .meteor/versions has meteor@1.11.5 and meteor-base@1.5.1 in it.

I’ve been trying to upgrade figuratively everything, and my latest sticking point is alanning:roles. I’ve upgraded to 3.6.3 just fine so far, but after upgrading to 4.0 things broke and I’m not sure why or how to fix it.

The top-level App component has these higher-order components wrapping it:

export default withTrackerSsr(() => {
  const app = Meteor.subscribe('app');
  const loggingIn = Meteor.loggingIn();
  const user = Meteor.user();
  const userId = Meteor.userId();
  const loading = !app.ready() && !Roles.subscription.ready();
  const name = user?.profile?.name && getUserName(user.profile.name);
  const emailAddress = user?.emails?.[0]?.address;

  return {
    loading,
    loggingIn,
    authenticated: !loggingIn && !!userId,
    name: name || emailAddress,
    userId,
    emailAddress,
    emailVerified: user?.emails?.[0]?.verified ?? true,
  };
})(withCookies(App));

And this is what withTrackerSsr looks like:

import React from 'react';
import { Meteor } from 'meteor/meteor';
import { useTracker } from 'meteor/react-meteor-data';

export const useTrackerSsr = (trackerFn, deps = []) => {
  return useTracker(() => {
    if (Meteor.isClient) {
      return trackerFn();
    }
    return {};
  }, deps);
};

// For backwards compatibility, also export a HOC version
export default (container) => (Component) => {
  return function WithTrackerSsrWrapper(props) {
    const data = useTrackerSsr(() => container(props), [props]);
    return <Component {...props} {...data} />;
  };
};

After upgrading to alanning:roles 4, Roles.subscription stops existing, and Node complains. As far as I can tell, I don’t need it to exist because I don’t have any instances of .roles in my codebase that’re client-side.

However, when I remove && !Roles.subscription.ready() from the function passed in to withTrackerSsr, the site breaks — when I go into the DOM inspector on any page, <div id="react-root"></div> is empty. Worse, I get zero error messages hinting at where I should look to fix anything.

I have the following run at startup, but I’m not sure if I even ought to need this, given my (possibly mistaken) belief that I’m not using any roles subscriptions (and I’d rather not, since it seems like a low-grade privacy or security leak):

Meteor.publish(null, function () {
  if (!this.userId) {
    return this.ready();
  }
  return Meteor.roleAssignment.find({ 'user._id': this.userId });
});

All my usual LLMs have been unhelpful — they misdiagnose the problem and tell me I should keep && !Roles.subscription.ready(), but if I do what the LLMs suggest, then I get this error in the browser console:

Uncaught TypeError: Cannot read properties of undefined (reading ‘ready’)

…and the line pointed to is the line that reads

  const loading = !app.ready() && !Roles.subscription.ready();

, and the column is column 55 (the second .ready()).

Also, if I downgrade to alanning:roles 3.6.3 and remove the && !Roles.subscription.ready() bit of code, the site fails to load the same way (<div id="react-root"></div> is empty).

What can I do to figure out what’s wrong, and fix this so I can get my site working with alanning:roles 4.x?

Thanks!

There is no Roles.subscription in v4. If you want to make sure it’s ready, there are 2 options:

  1. Check if roles or/and role-assignment has data
  2. Change your publication on your server, instead of publish null name, use a real name and subscribe it on the client side.

Roles package needs that publication to work on front-end.

Thanks a bunch for your help.

This is what I ended up doing:

I started publishing roles on the server side in my startup code:

Meteor.publish('roles', function publishMeteorRoles() {
  if (!this.userId) {
    return this.ready();
  }
  return Meteor.roleAssignment.find({ 'user._id': this.userId });
});

And then started listening for them on the client side, in the App component:

const App = (props) => { /* … */ }

/**
 * This is `useTracker` as a higher-order component.
 *
 * The modern thing would be to use `useTracker` directly, but a lot of our code assumes that, say, `authenticated` is passed to our components via `props`.
 *
 * Additionally, by using a HOCified `useTracker` we don’t have to deal with the linter complaining about many, many shadowed variables.
 */
const withTracker = (container) => (Component) => {
  return function WithTrackerWrapper(props) {
    const data = useTracker(() => container(props), [props]);
    return <Component {...props} {...data} />;
  };
};

export default withTracker(() => {
  Meteor.subscribe('roles');
  const loggingIn = Meteor.loggingIn();

  const user = Meteor.user();
  const name = user?.profile?.name && getUserName(user.profile.name);
  const emailAddress = user?.emails?.[0]?.address;

  const userId = Meteor.userId();
  let loading = false;
  if (userId) {
    loading = !user;
  }

  return {
    loading,
    loggingIn,
    authenticated: !loggingIn && !!userId,
    name: name || emailAddress,
    userId,
    emailAddress,
    emailVerified: user?.emails?.[0]?.verified ?? true,
  };
})(withCookies(App));

I also had an LLM turn App into a function-style component (it was a class-based component previously). That seems to have helped. From what I can tell, I had some racy code that would show a blank screen every time for me but work OK on a colleague’s laptop…when it wasn’t plugged in (and presumably using more low-power cores).

Roles reactivity in Meteor 3 with alanning:roles@4.0.0? gave me an idea of where to put the Meteor.subscribe() call, but I commented out both it and the Meteor.publish('roles', …) parts and the site still seems OK.

Since I can’t break my application by commenting things out…

For the benefit of future readers, did I place the Meteor.publish() and Meteor.subscribe() calls in sensible places?

Thanks!