Closed TomJWilliams closed 2 years ago
The specific issue is if the content persisted in the cache contains dome elements with inline styles it will fail the JSON.parse with an unexpected token.
In this case the rich text editor added an inline style of style=\"margin-left:0px;\"
which generates the error invalid token m at position xxx. In this instance the character is m however, I believe this would be any character with an inline style generated by a rich text editor on a CMS. I need to get into work I will take another look at this later this evening.
After reviewing this more this issue is directly related to the way the content is parsed during persistence. Specifically the escaping of quotations, \n, or \t characters. I have included a snippet below which illustrates the example and highly levies code from the transfer-state.service.ts class.
// Function to parse scully state this is based off the pluc functionality and is the same less syntactical sugar
function _u(key, t) {
// Create the state tree
const stateTree = t.split('/** ___SCULLY_STATE_START___ */')[1].split('/** ___SCULLY_STATE_END___ */')[0];
// Retrieve the record from the state tree
let record = stateTree.split(`"${key}":`)[1];
if (!record) {
return;
}
let parsedRecord = record.replace(/_~d~/g,'\\\\"').replace(/_~[^]~/g, (s) => interpolationCharacters[s]).replace(/\\n/g,'\\\\n').replace(/\\t/g,'\\\\t').replace(/\\r/g,'\\\\r');
console.log(parsedRecord);
let jsonRecord = JSON.parse(parsedRecord);
//console.log(parsedRecord);
}
// Moq uncorrected scully state
let uncorrectedScullyState = '/** ___SCULLY_STATE_START___ */{"getArticles-LIVE-true":{"data":{"articles":[{"__typename":"Article","id":"1","slug":"test-article","title":"Test article","description":"_~l~p_~g~This is a test article to validate image support_~l~_~s~p_~g~","featured":false,"author":{"__typename":"Writer","id":"1","name":"David Doe","slug":"david-doe","email":"daviddoe@strapi.io","picture":{"__typename":"UploadFile","id":"24","url":"https:_~s~_~s~the-enterprise-programmer-cms-assets.azureedge.net_~s~cms_~s~assets_~s~daviddoe_strapi_io_3d3d8df96b.jpg","created_at":"2021-09-12T14:17:18.000Z","updated_at":"2021-09-12T14:17:18.000Z","hash":"daviddoe_strapi_io_3d3d8df96b","mime":"image_~s~jpeg","name":"daviddoe@strapi.io.jpg","provider":"azure-storage","size":587.69},"created_at":"2021-09-12T02:56:18.000Z","updated_at":"2021-09-17T13:49:26.000Z"},"categories":[{"__typename":"Category","id":"1","name":"food","slug":"food","created_at":"2021-09-12T02:56:15.000Z","updated_at":"2021-09-24T22:09:23.000Z"}],"image":{"__typename":"UploadFile","id":"22","url":"https:_~s~_~s~the-enterprise-programmer-cms-assets.azureedge.net_~s~cms_~s~assets_~s~a_bug_is_becoming_a_meme_on_the_internet_827a5a0d22.jpg","created_at":"2021-09-12T14:17:18.000Z","updated_at":"2021-09-17T12:47:55.000Z","hash":"a_bug_is_becoming_a_meme_on_the_internet_827a5a0d22","mime":"image_~s~jpeg","name":"a-bug-is-becoming-a-meme-on-the-internet.jpg","provider":"azure-storage","size":198.85},"published_at":"2021-09-17T13:46:26.000Z","created_at":"2021-09-17T13:42:26.000Z","updated_at":"2021-10-20T02:14:05.000Z","content":"_~l~p_~g~This is some simple text and we have an HTML snippet however there is no block quote lets see if this causes an issue._~l~_~s~p_~g~_~l~p style=\"margin-left:0px;\"_~g~ _~l~_~s~p_~g~_~l~p style=\"margin-left:0px;\"_~g~HTML:_~l~_~s~p_~g~_~l~div class=\"raw-html-embed\"_~g~_~l~div class=\"article-body-quote-container\"_~g~\n\t_~l~div class=\"row\"_~g~\n \t_~l~div class=\"col md-4\"_~g~\n \t_~l~div class=\"card\"_~g~\n \t\t\t_~l~div class=\"card-body\"_~g~\n \t_~l~div class=\"d-flex flex-row justify-content-center\"_~g~\n \t\t_~l~i class=\"fas fa-quote-left pe-2\"_~g~_~l~_~s~i_~g~\n \t\t_~l~p class=\"px-xl-3\"_~g~\n \t\tLorem ipsum dolor sit amet,\n \t\tconsectetur adipisicing elit. Quod eos id officiis hic tenetur\n \t\tquae quaerat ad velit ab hic tenetur.\n \t\t_~l~_~s~p_~g~\n \t\t_~l~i class=\"fas fa-quote-left pe-2\"_~g~_~l~_~s~i_~g~\n \t_~l~_~s~div_~g~\n \t_~l~_~s~div_~g~\n _~l~_~s~div_~g~\n _~l~_~s~div_~g~\n _~l~_~s~div_~g~\n_~l~_~s~div_~g~_~l~_~s~div_~g~"}]}}/** ___SCULLY_STATE_END___ */'
// Moq corrected scully state
let correctedScullyState = '/** ___SCULLY_STATE_START___ */{"getArticles-LIVE-true":{"data":{"articles":[{"__typename":"Article","id":"1","slug":"test-article","title":"Test article","description":"_~l~p_~g~This is a test article to validate image support_~l~_~s~p_~g~","featured":false,"author":{"__typename":"Writer","id":"1","name":"David Doe","slug":"david-doe","email":"daviddoe@strapi.io","picture":{"__typename":"UploadFile","id":"24","url":"https:_~s~_~s~the-enterprise-programmer-cms-assets.azureedge.net_~s~cms_~s~assets_~s~daviddoe_strapi_io_3d3d8df96b.jpg","created_at":"2021-09-12T14:17:18.000Z","updated_at":"2021-09-12T14:17:18.000Z","hash":"daviddoe_strapi_io_3d3d8df96b","mime":"image_~s~jpeg","name":"daviddoe@strapi.io.jpg","provider":"azure-storage","size":587.69},"created_at":"2021-09-12T02:56:18.000Z","updated_at":"2021-09-17T13:49:26.000Z"},"categories":[{"__typename":"Category","id":"1","name":"food","slug":"food","created_at":"2021-09-12T02:56:15.000Z","updated_at":"2021-09-24T22:09:23.000Z"}],"image":{"__typename":"UploadFile","id":"22","url":"https:_~s~_~s~the-enterprise-programmer-cms-assets.azureedge.net_~s~cms_~s~assets_~s~a_bug_is_becoming_a_meme_on_the_internet_827a5a0d22.jpg","created_at":"2021-09-12T14:17:18.000Z","updated_at":"2021-09-17T12:47:55.000Z","hash":"a_bug_is_becoming_a_meme_on_the_internet_827a5a0d22","mime":"image_~s~jpeg","name":"a-bug-is-becoming-a-meme-on-the-internet.jpg","provider":"azure-storage","size":198.85},"published_at":"2021-09-17T13:46:26.000Z","created_at":"2021-09-17T13:42:26.000Z","updated_at":"2021-10-20T02:14:05.000Z","content":"_~l~p_~g~This is some simple text and we have an HTML snippet however there is no block quote lets see if this causes an issue._~l~_~s~p_~g~_~l~p style=\\"margin-left:0px;\\"_~g~ _~l~_~s~p_~g~_~l~p style=\\"margin-left:0px;\\"_~g~HTML:_~l~_~s~p_~g~_~l~div class=\\"raw-html-embed\\"_~g~_~l~div class=\\"article-body-quote-container\\"_~g~\\n\\t_~l~div class=\\"row\\"_~g~\\n \\t_~l~div class=\\"col md-4\\"_~g~\\n \\t_~l~div class=\\"card\\"_~g~\\n \\t\\t\\t_~l~div class=\\"card-body\\"_~g~\\n \\t_~l~div class=\\"d-flex flex-row justify-content-center\\"_~g~\\n \\t\\t_~l~i class=\\"fas fa-quote-left pe-2\\"_~g~_~l~_~s~i_~g~\\n \\t\\t_~l~p class=\\"px-xl-3\\"_~g~\\n \\t\\tLorem ipsum dolor sit amet,\\n \\t\\tconsectetur adipisicing elit. Quod eos id officiis hic tenetur\\n \\t\\tquae quaerat ad velit ab hic tenetur.\\n \\t\\t_~l~_~s~p_~g~\\n \\t\\t_~l~i class=\\"fas fa-quote-left pe-2\\"_~g~_~l~_~s~i_~g~\\n \\t_~l~_~s~div_~g~\\n \\t_~l~_~s~div_~g~\\n _~l~_~s~div_~g~\\n _~l~_~s~div_~g~\\n _~l~_~s~div_~g~\\n_~l~_~s~div_~g~_~l~_~s~div_~g~"}]}}/** ___SCULLY_STATE_END___ */'
// Invoke function
_u('getArticles-LIVE-true',uncorrectedScullyState);
@TomJWilliams Thanks for digging into this.
Solving it is a bit hard, as the code is fully generated from the service. There is meta-escaping involved.
Also, embedding HTML content in the transfer-state might not be the ideal solution.
You can use scully-content
for that. You can do that on a per route base, and pull the content during build using a function plugin
'/routeByCMS/:id' : {
type: default, // or whatever
postRenderers: ['contentText'], // if you have defaultRenderes, pull them in!
contentType: 'html', (or HTML or plain text, or whatever)
content: async (route:HandledRoute) => {/** extract Id from route, fetch content from CMS and returnit as sting **/ {
}
However, I would still love to get a PR that solves this issue. As I'm currently lacking the time to prioritize this issue.
@SanderElias My apologies for the delay in response. I do not disagree with you. I struggled over the decision to use the transfer-state service. Ultimately what led to the decision is that this content is dynamic, fed from a user query via the CMS. Thus I am using transfer-state to cache the initial response from the service decreasing the page load time. Then if the user changes the query, the key for the cache does not match and the service is called instead. I believe the core of the issue is the meta-escaping and the fact that the HTML with class information is returned via Strapi. Let me look into this more, I am also working on potentially changing out the backend to Sanity. This changes the structure of what the cache would be and I can refactor my Angular app to use a component factory. This should also resolve the fundamental issue given that Sanity's data structure is fundamentally different than html.
@TomJWilliams I'm beginning to think we need a separate way of storing arbitrary data, outside of the page.
Just like we do with the data.json
which carries a copy of the date in the transferState.
Or, perhaps we need something that works similar to scully-content but saves the data into templates outside of the angular app.
It is whole well possible to build plugins that already do those things.
I have been looking into this one more, and it pretty much is a duplicate of #1567. So, I'm closing this issue, to track progress there. (also, the PR targeting that bug will for 95% close this one too)
🐞 Bug report
First let me state that the team at HeroDevs does an outstanding job and I thoroughly appreciate all of the effort in the creation and development of Scully.
Description
When attempting to retrieve data from the transfer-state service an exception is thrown when attempting to parse the json in the cached state. My reason for bringing this up is the increased adoption with strapi and similar headless cms options. Many use ckeditor or another WYSIWYG editor which will add styling to the object graph as it is added to the content.
I can see that @SanderElias made a commit 9 months, as seen below, ago for the handling of quotes and escaping them for the private saveState method.
🔬 Minimal Reproduction
Set the state with an observable returning json content, in my case from a CMS, where the object graph includes embedded styled components.
💻Your Environment
Angular Version:
Scully Version:
🔥 Exception or Error
This is the code embedded in the static file which includes the JSON content that is attempting to be parsed from the cache at the time of retrieving the value from the cache with the pluck rxJs method.