Make the backend URL a global setting

I think I misunderstood you as well :smile:

It seems that https://artistintheshed.wordpress.com is redirected to https://artistintheshed.com, and if we take a look at the API, https://public-api.wordpress.com/wp/v2/sites/artistintheshed.com/, seems to work fine.

With this in mind I assume that:

  • state.source.url: https://artistintheshed.com.
  • state.frontity.url: http://localhost:3000 or wherever your Frontity app is.

I guess you will have to define somewhere that state.source.url is a WP.com site, but I am not sure what is the best way to do this, I guess you would need to set state.source.isWpCom: true.

Could you help us here @mmczaplinski please?

If state.source.url is set to https://artistintheshed.com then I get a 404 - I guess because that’s not a valid link to the API. Adding state.source.isWpCom: true doesn’t make a difference.

If state.source.url is set to https://artistintheshed.wordpress.com then it works as that points to the API at wordpress.com.

But then the link component problem that I describe in the loom video occurs - I guess because the internal links pointing to artistintheshed.com/whatever don’t match the hostname part of the URL which is artistintheshed.wordpress.com as far as Frontity is concerned.

By the way, this site is just an example because I know they have a custom URL. This problem will occur for any wordpress.com site with a custom URL.

@SantosGuillamot Let me know if this is what you expected me to explain :sweat_smile:


I think that there is some confusion because we have this bug that @mburridge has reported: https://github.com/frontity/frontity/issues/622.

As a matter of fact, the solution that Michael posted in the github issue is probably correct (or very close to what we it should be) so thanks Michael! :slight_smile:

So, assuming that the bug I’ve mentioned is fixed, we should be able to do either of two things:

Version 1.

  • state.source.url: https://artistintheshed.wordpress.com

  • state.frontity.url: http://localhost:3000 or wherever your Frontity app is.

    The url points directly to a site that ends in wordpress.com so frontity can detect that and set up state.source.api correctly.

Version 2.

  • state.source.url: https://artistintheshed.com

  • isWpCom: true

  • state.frontity.url: http://localhost:3000 or wherever your Frontity app is.

    In this case, frontity does not know that https://artistintheshed.com is a wp.com site, so we have to tell Frontity that :slight_smile:

Hope this helps!

Great, thanks @mmczaplinski! I think that after solving the issue you mention the link processor should work fine with these settings.

I thought that wordpress.com sites with custom domains had the REST API in https://domain.com/wp-json but that doesn’t seem to be the case for https://artistintheshed.com.

Maybe that only happens in the Bussiness plan, the one where you can add custom plugins, but not in the Personal or Premium plan.

At least https://public-api.wordpress.com/wp/v2/sites/artistintheshed.com/ seems to work fine, so I guess we should promote the use of https://artistintheshed.com in state.source.url and not https://artistintheshed.wordpress.com for that case, which is what Michal proposed in version 2:

But I don’t think that is working right now, is it? Because state.source.api is not checking state.source.isWpCom to check if the site is a WordPress or not, it is doing its own logic:

Code in: https://github.com/frontity/frontity/blob/dev/packages/wp-source/src/state.ts#L73-L87

{
  api: ({ state }) => {
    // Check if it's a free WordPress.com site.
    if (/^https:\/\/(\w+\.)?wordpress\.com/.test(state.source.url))
      return addFinalSlash(
        `https://public-api.wordpress.com/wp/v2/sites/${state.source.url}`
      );

    return addFinalSlash(
      addFinalSlash(state.source.url) + state.wpSource.prefix.replace(/^\//, "")
    );
  },
  isWpCom: ({ state }) =>
    state.source.api.startsWith(
      "https://public-api.wordpress.com/wp/v2/sites/"
    ),
};

If I am right, I guess we need to use state.source.isWpCom inside state.source.api. Something like this:

{
  api: ({ state }) => {
    // Check if it's a WordPress.com site (free, personal or premium).
    if (state.source.isWpCom)
      return addFinalSlash(
        `https://public-api.wordpress.com/wp/v2/sites/${state.source.url}`
      );

    return addFinalSlash(
      addFinalSlash(state.source.url) + state.wpSource.prefix.replace(/^\//, "")
    );
  },
};

Of course also with the fix proposed by @mburridge that removes the protocol from the URL: https://github.com/frontity/frontity/issues/622#issuecomment-732139397.

You’re right about checking the isWpCom @luisherranz . Ideally, we should check if that’s the case inside state.source.api but I’ve realized that the problem is that this would introduce mutual infinite recursion :recycle: :fearful:

:point_up: The state.source.api is checking the state.source.isWpCom in it’s definition and vice versa. So if the user does not define the isWpCom themselves, you’ll get a “Maximum stack call exceeded” exception.

However, we can check if the state is a derived state using Object.getOwnPropertyDescriptor()
I think we will then need a slightly more complex check like this:

{
  api: ({ state }) => {
    // Check if it's a free WordPress.com site.
    // We have to make sure the `.isWpCom` is NOT a derived state because otherwise 
    // we would end up in an infinite loop.
    if (
      typeof Object.getOwnPropertyDescriptor(state.source, "isWpCom").value ===
        "boolean" &&
      state.source.isWpCom
    ) {
      return addFinalSlash(
        `https://public-api.wordpress.com/wp/v2/sites/${state.source.url}`
      );
    }

    return addFinalSlash(
      addFinalSlash(state.source.url) + state.wpSource.prefix.replace(/^\//, "")
    );
  },
  isWpCom: ({ state }) =>
    // The user can define either the `state.source.url` or `state.souce.api`
    // so we should try to detect either of those cases automatically. 
    /^https:\/\/(\w+\.)?wordpress\.com/.test(state.source.url) ||
    state.source.api.startsWith(
      "https://public-api.wordpress.com/wp/v2/sites/"
    ),
}

This will still fail if the user has a site on wordpress.com with a custom domain but has NOT specified "isWpCom": true. I think that there is nothing that we can do about it though because if the user chooses to use their custom domain, Frontity cannot just know if it’s a wp.com or wp.org.

So, we will have to make it explicit in the documentation that when using a wp.com site with a custom domain, the user has to pass the isWpCom: true in their settings.

Nice trick! Maybe we could even release a isDerived() tool based on that code :slightly_smiling_face:

const isDerived = (obj, propName) =>
  typeof Object.getOwnPropertyDescriptor(obj, propName).value === "function";

Regarding the code, I think you still have to check inside state.source.api if state.source.url is from a subdomain.wordpress.com site because you cannot rely on state.wpSource.isWpCom for that:

const api = ({ state }) => {
  // Is it a WordPress.com site with a custom domain?
  const isCustomWpCom =
    !isDerived(state.wpSource, "isWpCom") && state.wpSource.isWpCom;
  // Is it a free WordPress.com site using a subdomain.wordpress.com domain?
  const isFreeWpCom = /^https:\/\/(\w+\.)?wordpress\.com/.test(
    state.source.url
  );

  if (isCustomWpCom || isFreeWpCom) {
    const { hostname } = new URL(state.source.url);
    return addFinalSlash(
      `https://public-api.wordpress.com/wp/v2/sites/${hostname}`
    );
  }

  return addFinalSlash(
    addFinalSlash(state.source.url) + state.wpSource.prefix.replace(/^\//, "")
  );
};

Don’t you think?

yep, you’re right about that and that’s a clearer implementation as well! :slight_smile:

Awesome. I have just opened a FD for the isDerived util :slightly_smiling_face:

We have a lot of cases so I have made a table to make sure we don’t forget any of them and we add tests for all of them.

state.frontity.url state.source.url state.source.api state.wpSource.isWpCom
Free WP com - configured by state.source.url Set by the user: final-domain.com Set by the user: sub.wordpress.com Derived from state.source.url: sub.wordpress.com/wp-json Derived from state.source.api: true
Free WP com - configured by state.source.api Set by the user: final-domain.com Derived from state.frontity.url: final-domain.com Set by the user: public-api.wordpress.com/wp/v2/sites/sub.wordpress.com Derived from state.source.api: true
Personal and Premium WP com - configured by state.source.url and state.wpSource.isWpCom Set by the user: final-domain.com Set by the user: final-domain.com Derived from state.source.url: public-api.wordpress.com/wp/v2/sites/final-domain.com Set by the user: true
Personal and Premium WP com - configured by state.source.url and state.source.api Set by the user: final-domain.com Set by the user: final-domain.com Set by the user: public-api.wordpress.com/wp/v2/sites/final-domain.com Derived from state.source.api: true
Personal and Premium WP com - configured by state.source.api Set by the user: final-domain.com Derived from state.frontity.url: final-domain.com Set by the user: public-api.wordpress.com/wp/v2/sites/final-domain.com Derived from state.source.api: true
WP org and Business WP com - configured by state.source.url Set by the user: final-domain.com Set by the user: wp-domain.com Derived from state.source.url: wp-domain.com/wp-json Derived from state.source.api: false
WP org and Business WP com - configured by state.source.api Set by the user: final-domain.com Derived from state.frontity.url: final-domain.com Set by the user: wp-domain.com/wp-json Derived from state.source.api: false

You can use https://www.tablesgenerator.com/markdown_tables if you want to edit the table. I think copy/pasting shoud work.

Last thing: WordPress.com has 5 plans (https://wordpress.com/pricing/). These are the relations with the configuration:

  • Free: Free WP com
  • Personal and Premium: Personal and Premium WP com
  • Business: WP org and Business WP com (these sites have a final-domain.com/wp-json so they can act as regular WP org sites).
  • eCommerce: I have no idea, to be honest.
1 Like

Does this change have any implications for libraries.source.api.init? Do we need to make any changes to the documentation there?

https://docs.frontity.org/api-reference-1/wordpress-source#libraries-source-api-init

I don’t understand some of the cases you mentioned on the table.

When you talk about these three specifically:

  • Free WP com - configured by state.source.api
  • Personal and Premium WP com - configured by state.source.api
  • WP org and Business WP com - configured by state.source.api

What do you mean exactly when you say that state.source.url is derived from state.frontity.url? Shouldn’t it be derived from state.source.api?

I thought state.source.url should point to the WordPress instance, not the Frontity one. Except for the embedded mode: in that case, I guess state.frontity.url and state.source.url would be the same.

I don’t know why that API is documented, to be honest. That init is internal and not meant to be used by other packages, only by the source package.

In this specific case, we even have plans to remove it in the not so distant future. So could you please remove it from the docs? :slightly_smiling_face:

I think we never thought that state.source.url could be derived from state.source.api if state.source.api is defined by the user (not derived) but I guess that with Michal’s isDerived function we can do that.

Actually, that would mean that state.source.url is backward compatible and safer to use than it is today.

Is it feasible technically to do it?

Yup, it is. I already wrote the tests for that and all of them are passing. So, yeah. :+1:

Ok, is this table correct then?

state.frontity.url state.source.url state.source.api state.source.isWpCom
Free WP com - configured by state.source.url Set by the user: final-domain.com Set by the user: sub.wordpress.com Derived from state.source.url: public-api.wordpress.com/wp/v2/sites/sub.wordpress.com Derived from state.source.api: true
Free WP com - configured by state.source.api (backward compatibility only) Set by the user: final-domain.com Derived from state.source.api: sub.wordpress.com Set by the user: public-api.wordpress.com/wp/v2/sites/sub.wordpress.com Derived from state.source.api: true
Personal and Premium WP com - configured by state.source.url and state.wpSource.isWpCom Set by the user: final-domain.com Set by the user: final-domain.com Derived from state.source.url: public-api.wordpress.com/wp/v2/sites/final-domain.com Set by the user: true
Personal and Premium WP com - configured by state.source.url and state.source.api (backward compatibility only) Set by the user: final-domain.com Set by the user: final-domain.com Set by the user: public-api.wordpress.com/wp/v2/sites/final-domain.com Derived from state.source.api: true
Personal and Premium WP com - configured by state.source.api (backward compatibility only) Set by the user: final-domain.com Derived from state.source.api: final-domain.com Set by the user: public-api.wordpress.com/wp/v2/sites/final-domain.com Derived from state.source.api: true
WP org and Business WP com - configured by state.source.url Set by the user: final-domain.com Set by the user: wp-domain.com Derived from state.source.url: wp-domain.com/wp-json Derived from state.source.api: false
WP org and Business WP com - configured by state.source.api (backward compatibility only) Set by the user: final-domain.com Derived from state.source.api: wp-domain.com Set by the user: wp-domain.com/wp-json Derived from state.source.api: false

I am not taking into account state.wpSource.api and state.wpSource.isWpCom now.

Awesome. Did you implement isDerived already? isDerived util

I have done a graph of the logic, but I would like to review this with you. Let me know whenever you can and we will jump in a quick call.

I would like to also review with you:

  • state.wpSource.api
  • state.wpSource.isWpCom

Ok, we have carefully reviewed the logic and I think we got it right this time :slightly_smiling_face:

Not only that, but thanks to @mmczaplinski’s isDerived function and @David who noticed that we can use that to derive state.source.url from state.source.api, now state.source.url is backward compatible and safe to use in any package!! :tada::tada:.

Awesome work guys :slightly_smiling_face:

Thanks also to @mburridge for bringing into our attention the problem with wordpress.com sites that have custom domains but can’t use /wp-json.

This is the final logic @david is implementing:

@luisherranz I’ve created this issue.

1 Like