markhuot / craftql

A drop-in GraphQL server for Craft CMS
Other
320 stars 53 forks source link

Enhancement: Support For Live Preview / Preview Links #31

Open edolyne opened 6 years ago

edolyne commented 6 years ago

It would be great if there was a way to enable graphql to work via live preview and preview links.

For preview links, potentially leveraging the token as an argument to pass which can then fetch the draft or return an error if the token has expired.

For example:

{
  entry(slug: "test", token: "gO9vhGh4WgicL-bjUr78VKxT7MMw2zqW") {
    title
    id
  }
}

For live preview, I'm not quite sure about a proposed solution as I'm not sure if or how craft stores the live preview.

markhuot commented 6 years ago

I’ll need to look into this. The last I checked, at least in Craft 2, preview data was not exposed through the API. It was only sent as POST data directly to the entry template, which we don’t have access to query directly.

markhuot commented 6 years ago

After looking in to this more I don't think it's possible for CraftQL, alone, to manage live preview URLs. The way Craft POSTs preview data directly to a template makes it impossible for CraftQL to intercept. You, could, however use the drafts field on and EntryConnection to pull Draft's out of the database to approximate this experience.

timkelty commented 6 years ago

I looks like Live Preview will become tokenized in the future, which may help: https://github.com/craftcms/cms/issues/1521

markhuot commented 6 years ago

Wider token based authentication for Craft would be a welcome addition indeed. There's a lot of token management and validation I'd love to rip out of CraftQL. This is only half the battle though. Right now live previews are not stored in the database, they're POST data sent to the twig view directly. For CraftQL to be able to read preview data it has to be stored somewhere more permanent. I'm excited for tokens, but there's still a ways to go before this is possible.

All that said, I'll browse through the https://github.com/craftcms/cms issue list and see if moving previews away from POST data is on the horizon and if not I'll open an issue to ask about it.

timkelty commented 6 years ago

I'll browse through the https://github.com/craftcms/cms issue list and see if moving previews away from POST data is on the horizon and if not I'll open an issue to ask about it.

See @narration-sd's reply to my comment: https://github.com/craftcms/cms/issues/1521#issuecomment-409895388

oddnavy commented 5 years ago

Is it now possible to access live preview content using the "Tokenized Live Preview authorization" added in craftcms/cms#1521?

Testing locally it seems like if I add a valid X-Craft-Token header, the graphql api attempts to return the twig template for the entry rather than the JSON. If I remove the X-Craft-Token header the request works prefectly.

markhuot commented 5 years ago

I’m looking in to this more. I don’t think the X-Craft-Token gets us preview content, that content is still hidden away in a POST request. But I’m working on an update to CraftQL that’ll store that POST data and make it available to GraphQL when the header token is present.

I’m going to re-open this ticket because I have an idea on how to implement this.

narration-sd commented 5 years ago

@oddnavy @markhuot Actually, that's just right, Mark. There is no data present for CraftQL to see during live updates.

I've got a full solution about ready to go out the door. It takes much more than you may imagine to do this right -- essential safety, efficiency, and flexibility as well as correctness, and then get it hooked in a useful fashion to SPA or embedded-component sites.

And the data availability side is far from the only issue in providing something others can readily work with in practice.

What I've got has properly had a long gestation. It's called Live Vue, is a Craft plugin which will be priced like yours, and will go out initially for Gatsby, which has its own issues, and drew me into one more reconstitution of the architecture,

I'm doing it this way for all platforms now because I like the design result Gatsby teased me into. Much for the developer using it is simplified, achieving that goal.

This will finally close off a large area of issues which I had solved and refined to a limit, but still concerned I feel rather accurately about how much trouble it would be in terms of handholding, to field the original.

Which has long been working solidly on Vue, where it was first worked up, for many months. The Gatsby effort gets me also to quite solid working with React as well. I'm proceeding through a limited beta as much for community 'chiefs' confidence as much as to see that readiness is there. Not long now.

narration-sd commented 5 years ago

...and by the way, I should say thanks again, for CraftQL. This thing started out on Element API, and then had some 'interesting' and potential abilities, but once I saw how clear it was to put together many tasks persons ordinarily want to do, I was sold on your path. It'll be a boon to many, and has already been here.

Element API remains also a part of Live Vue, so both are going to be covered.

monachilada commented 5 years ago

The tokenized preview feature has now been added to the Craft 3.2 alphas! :) See commit: https://github.com/craftcms/cms/commit/197ce312dd641dcad5838fcb442dfef27da538c1

From @brandonkelly's description:

Preview requests will have a token query string parameter, and as long as you pass that along to your Element API (or other API) request, the draft content will be returned instead of the main entry content.

This works great in terms of what gets passed to the Live Preview iframe, but I'm struggling to figure out in which way that token needs to be passed through my CraftQL queries or requests to get it to work, or even whether changes need to be made to the plugin itself to enable it. Can you chime in @markhuot?

I'd be happy to help out if I can, just let me know.

brandonkelly commented 5 years ago

@monachilada Add something like this to your JS:

// Get the preview token from the URL
let m = document.location.href.match(/\btoken=([^&]+)/);
let token = m ? m[1] : '';

// Then forward that on whenever you are sending a CraftQL API request
let url = `...?token=${token}`;
// ...

If you’ve set a custom tokenParam config setting, use that instead of token here.

CraftQL uses element queries internally to fetch its content, so in theory this will just work, and you’ll start seeing draft content sent back when a token is present.

luke-underwood commented 5 years ago

@brandonkelly 🤔 I'm getting CORS issues when forwarding the token to my SPA. Happening on both 3.2 beta and alpha

Access to fetch at 'https://localhost/api?token=MtitB8j722_Qwo45bYOAdDXKQmfM-hJT'
 from origin 'https://localhost:3000' has been blocked by CORS policy: 
Response to preflight request doesn't pass access control check: 
No 'Access-Control-Allow-Origin' header is present on the requested resource. 
If an opaque response serves your needs, set the request's mode to 'no-cors' 
to fetch the resource with CORS disabled.

I already have allowedOrigins set in my craftql.php config:

<?php

// Set allowed origins for CraftQL
return [
    'allowedOrigins' => [
        'https://localhost:3000'
    ]
];

Works fine when I don't supply the token

EDIT: Now working since I've disabled CSRF protection in general config file: 'enableCsrfProtection' => false,

brandonkelly commented 5 years ago

@SnarkieDesign I’ve just disabled CSRF validation for preview requests, for the next 3.2 Beta release (craftcms/cms@54831ac139c954f2499fa81b977fed70803f8792).

To get the fix early, change your craftcms/cms requirement in composer.json to:

"require": {
  "craftcms/cms": "3.2.x-dev#54831ac139c954f2499fa81b977fed70803f8792 as 3.2.0-beta.2",
  "...": "..."
}

Then run composer update. And then you should be able to re-enable your enableCsrfProtection config setting.

narration-sd commented 5 years ago

https://github.com/craftcms/cms/issues/4584#issuecomment-512569473

codyjames commented 5 years ago

@brandonkelly Is the goal here that we won't have to write specific draft queries, but rather we'd just have to include the token in the request and it'd return the associated draft?

brandonkelly commented 5 years ago

@codyjames Yep that’s the goal. It’s not perfect yet for unsaved/disabled entries (see craftcms/cms#4581), but we’re getting there.

codyjames commented 5 years ago

@brandonkelly Okay great, that'll be so awesome. Yeah, I was running into the unsaved/disabled issue. It'd be fantastic if the token made it so the associated entry was considered "enabled" when the token was present.

brandonkelly commented 5 years ago

Yeah, that’s the general idea of how we could solve this. For unsaved drafts it’s a little more complicated because we also have to let it ignore the draft status for that one element.

brandonkelly commented 5 years ago

@codyjames Craft 3.2.5 should fix this. Details and info on how you can test here: https://github.com/craftcms/cms/issues/4581#issuecomment-513026880

codyjames commented 5 years ago

@brandonkelly Okay, this is working for me. I love it. We're using it for a Next.js app and it is working like a charm.

brandonkelly commented 5 years ago

Awesome!

brandonkelly commented 5 years ago

By the way, the official 3.2.5 release is out now, which fixes a bug introduced by my original commit referenced above. So make sure you’re running that.

Jones-S commented 5 years ago

I see there has been some progress on this issue. But what I don't really know:

  1. is it possible to get the newest draft via GraphQL by adding this new token from craft
  2. If so, how does a request look like. I can't my head get around how I should send that token. Also because my apollo client already sends the Bearer token to get access to the graphql service.

Thanks in advance. cheers

lukehmu commented 5 years ago

I see there has been some progress on this issue. But what I don't really know:

1. is it possible to get the newest draft via GraphQL by adding this new token from craft

2. If so, how does a request look like. I can't my head get around how I should send that token. Also because my apollo client already sends the Bearer token to get access to the graphql service.

Thanks in advance. cheers

Hi @Jones-S - I'm working on a Vue project where I'm using the CraftQL plugin. I have just got preview working correctly.

As @brandonkelly said here you need to apend the craft token as a query parameter onto the request URL.

Excuse the syntax, I'm using Vue Router to get my query params & I've rewritten it a little to be more readable - it's a ternary in my code:

// grab the API URL from my env file
let postURL = process.env.API_URL

// if my app is requested with the x-craft-preview query param
if (router.currentRoute.query['x-craft-preview']) {
  // update the URL I am going to use in my POST to include the token from the query params.
  const postURL = `${process.env.API_URL}?token=${router.currentRoute.query.token}`
}

Then I send it off via Axios in the regular way (I tried using Axios params, but it didn't work, hence changing the URL)

await axios.post(postURL, {
      query: myGraphQLQuery,
    }).then // other stuff

Lnk to where I do this in my code, hope this helps!

Jones-S commented 5 years ago

@lukehmu Thanks for your hint. I myself got some kind of a working thing. I checked for the x-craft-preview param as well and would then send another graphql query. Where I would normally fetch a specific post I woudl this time go for a draft:

gql`query getDraft($slug: String!) {
      entriesConnection(slug: $slug) {
        edges {
          node {
            id
            title
          }
          drafts {
            edges {
              draftInfo {
                draftId
                name
              }
              node {
                id
                title
                ... on News {
                  body {
                    totalPages
                    content
                  }
                }
              }
            }
          }
        }
      }
    }`;

Here I fetch drafts and then I would dive into the node (that's where the actual data lies). After getting that data I would just take the latest of the drafts.

If your option works, this would be great of course and much nicer. So if I understand correctly: By sending a graphql request via axios, while passing on the token, I could use the normal query for my page and I would automatically get the latest draft?...

lukehmu commented 5 years ago

@Jones-S I started to go down this route, too.

However, passing the token as a query string in the request URL works perfectly for me.

You can test this easily by previewing your entry, inspecting the preview, looking at the request URL and getting the token. Then hardcode the token in your app and you'll see the draft version! Then refactor to do it dynamically as per my example above.

Cheers.

Jones-S commented 5 years ago

@lukehmu I see what you are doing and I am just trying to replicate in my setup. My setup though makes use of apollo (nuxt apollo module). Would you have any hints on how to approach that?

Because normally if someone enters an url it would fetch the page via apollo normally. Now I added some middleware that checks for the crafts preview token. If present I want to get the draft data by adding the craft token, but I don't know how I would do that with an apollo call...

--- Edit: I now built a setup where I just use axios to fetch the data if I see a preview token and otherwise I use apollo.

Anyhow I see that when you create a new entry and there is no saved version of it, the iframe does not have a slug to call making it impossible to fetch the specific data:

<iframe class="lp-preview" src="http://localhost:3000/__temp_7PaFsnSy7PLRPZi2aE0S9GvgTdWjnBuGrqR6?x-craft-preview=ovNpBZAv7Q&amp;token=sjujuqH8_VUTO4LKPB8AIEoPrWgonhsR" frameborder="0"></iframe> @lukehmu Do you have a solution for that too? I would somehow need a way to send the slug...

ca-miked commented 5 years ago

@Jones-S I am running into the same issue.

Jones-S commented 5 years ago

@ca-miked You might want to check out craft cms 3.3 with graphl in its core. It will change a few things. You can set a headlessMode and in 3.4 there will be preview Targets: https://github.com/craftcms/cms/issues/4520

I hope it will solve the thing about the slugs of newly created posts. I am currently trying out the new setup.

Jones-S commented 5 years ago

@ca-miked indeed it is fixed. With Craft 3.4.x-dev (maybe even before) I can create a new entry, and as soon as I have set a title (and therefore a slug) it will open the preview with the correct slug, even if I haved not saved it before. 👍🏼