Meteor-Community-Packages / meteor-publish-composite

Meteor.publishComposite provides a flexible way to publish a set of related documents from various collections using a reactive join
https://atmospherejs.com/reywood/publish-composite
MIT License
553 stars 58 forks source link

this.userId is null on server when called from the client #185

Open wreiske opened 1 month ago

wreiske commented 1 month ago

Describe the bug I'm running into an issue where this.userId is null when called from a Meteor.subscribe on the client, but is NOT null when using this.subscribe inside fast-render server side.

Expected behavior this.userId should always include the current logged in userId calling the subscription either called server side (like in fast-render) or client side (using Meteor.subscribe).

Example

Client Side

FlowRouter.route('/billing', {
  name: 'BillingHome',
  waitOn: function () {
    console.log('Meteor userId is ', Meteor.userId()); // This outputs the ID correctly!
    return [
      import('../../ui/billing/billing.js'),
      Meteor.subscribe('UserData'),
      Meteor.subscribe('BillingPlans', 'client')
    ];
  },
  action() {
    renderSecuredRoute(this, 'BillingHome');
  }
});

Fast Render

Using communitypackages:fast-render@5.0.0-beta.0

FastRender.route('/billing', async function () {
  if (!this.userId) {
    return;
  }
  await this.subscribe('BillingPlans', 'server');
});

Server Side Publication


publishComposite('BillingPlans', async function (optional) {
  return {
    async find() {

      console.log('optional is =>', optional);

      console.log('before return, billingplans this.userId', this.userId);
      console.log('before return, billingplans Meteor.userId()', Meteor.userId());

      if (!this.userId) {
        console.error('billing plans not authorized');
        throw new Meteor.Error('not-authorized');
      }
      return BillingPlans.find({
        visible: { $ne: false }
      });
    }, children: [
      {
        find(plan) {
          return BillingSubscriptions.find({
            planId: plan._id,
            userId: this.userId
          });
        }
      }
    ]
  };
});

Current Behavior https://github.com/Meteor-Community-Packages/meteor-publish-composite/commit/ec9f07755708166267b92772a8fd1838448b4208

I20241013-20:00:10.686(0)? publishComposite called with userId: FQZPiRk46xdECpDpN
I20241013-20:00:10.686(0)? optional is => server
I20241013-20:00:10.686(0)? before return, billingplans this.userId FQZPiRk46xdECpDpN
I20241013-20:00:10.686(0)? before return, billingplans Meteor.userId() FQZPiRk46xdECpDpN
I20241013-20:00:11.606(0)? publishComposite called with userId: null
I20241013-20:00:11.606(0)? optional is => client
I20241013-20:00:11.606(0)? before return, billingplans this.userId null
I20241013-20:00:11.606(0)? before return, billingplans Meteor.userId() null
W20241013-20:00:11.607(0)? (STDERR) billing plans not authorized

With Meteor.bindEnvironment

import { Meteor } from 'meteor/meteor'

import Publication from './publication'
import Subscription from './subscription'
import { debugLog, enableDebugLogging } from './logging'

function publishComposite(name, options) {

  return Meteor.publish(name, function publish(...args) {
    const subscription = new Subscription(this);

    console.log('publishComposite called with userId:', this.userId); // Debug log for userId

    const boundEnvironment = Meteor.bindEnvironment(async () => {
      const instanceOptions = await prepareOptions.call(this, options, args);
      const publications = [];

      for (const opt of instanceOptions) {
        const pub = new Publication(subscription, opt);
        await pub.publish();
        publications.push(pub);
      }

      this.onStop(() => {
        for (const pub of publications) {
          pub.unpublish();
        }
      });

      await Promise.all(publications.map(p => p.awaitPromises()));
      debugLog('Meteor.publish', 'ready');
      this.ready();
    });

    // Invoke the async code within the bound environment
    boundEnvironment();
  });
}

// For backwards compatibility
Meteor.publishComposite = publishComposite;

async function prepareOptions(options, args) {
  let preparedOptions = options;

  if (typeof preparedOptions === 'function') {
    preparedOptions = await preparedOptions.apply(this, args);
  }

  if (!preparedOptions) {
    return [];
  }

  if (!Array.isArray(preparedOptions)) {
    preparedOptions = [preparedOptions];
  }

  return preparedOptions;
}

export {
  enableDebugLogging,
  publishComposite
}

Notice that the 'client' calls twice now. The second time it has the correct userId!!!!

I20241013-20:02:16.492(0)? publishComposite called with userId: FQZPiRk46xdECpDpN
I20241013-20:02:16.493(0)? optional is => server
I20241013-20:02:16.494(0)? before return, billingplans this.userId FQZPiRk46xdECpDpN
I20241013-20:02:16.494(0)? before return, billingplans Meteor.userId() FQZPiRk46xdECpDpN
I20241013-20:02:17.328(0)? publishComposite called with userId: null
I20241013-20:02:17.328(0)? optional is => client
I20241013-20:02:17.329(0)? before return, billingplans this.userId null
I20241013-20:02:17.329(0)? before return, billingplans Meteor.userId() null
W20241013-20:02:17.329(0)? (STDERR) billing plans not authorized
I20241013-20:02:17.401(0)? Exception in callback of async function: errorClass [Error]: [not-authorized]
I20241013-20:02:17.401(0)?     at Subscription.find (api/billing/server/publications.js:26:15)
I20241013-20:02:17.401(0)?     at Publication._getCursor (packages/reywood:publish-composite/lib/publication.js:95:36)
I20241013-20:02:17.401(0)?     at Publication.publish (packages/reywood:publish-composite/lib/publication.js:26:30)
I20241013-20:02:17.401(0)?     at packages/reywood:publish-composite/lib/publish_composite.js:20:19
I20241013-20:02:17.401(0)?     at processTicksAndRejections (node:internal/process/task_queues:95:5) {
I20241013-20:02:17.402(0)?   isClientSafe: true,
I20241013-20:02:17.402(0)?   error: 'not-authorized',
I20241013-20:02:17.402(0)?   reason: undefined,
I20241013-20:02:17.402(0)?   details: undefined,
I20241013-20:02:17.402(0)?   errorType: 'Meteor.Error'
I20241013-20:02:17.402(0)? }
I20241013-20:02:17.501(0)? publishComposite called with userId: FQZPiRk46xdECpDpN
I20241013-20:02:17.502(0)? optional is => client
I20241013-20:02:17.502(0)? before return, billingplans this.userId FQZPiRk46xdECpDpN
I20241013-20:02:17.502(0)? before return, billingplans Meteor.userId() FQZPiRk46xdECpDpN

IMO, Somewhere there is a bug that is causing this behavior... I am also running on about an hour of sleep so I may also be overlooking something simple.

github-actions[bot] commented 1 month ago

Thank you for submitting this issue!

We, the Members of Meteor Community Packages take every issue seriously. Our goal is to provide long-term lifecycles for packages and keep up with the newest changes in Meteor and the overall NodeJs/JavaScript ecosystem.

However, we contribute to these packages mostly in our free time. Therefore, we can't guarantee your issues to be solved within certain time.

If you think this issue is trivial to solve, don't hesitate to submit a pull request, too! We will accompany you in the process with reviews and hints on how to get development set up.

Please also consider sponsoring the maintainers of the package. If you don't know who is currently maintaining this package, just leave a comment and we'll let you know