[BUG] Integrity error when loading external js resource
Closed this issue · 56 comments
Oqtane Info
Version - 5.2.4
Render Mode - Static
Interactivity - Server
Database - SQL Server
Describe the bug
Sometimes when loading a js external resource, it fails due to 'integrity' issues with the computed hash. For example, I'm using Swiper.js and including it from its cdn:
new Resource {
ResourceType = ResourceType.Script,
Url = "https://cdn.jsdelivr.net/npm/swiper@11/swiper-element-bundle.min.js",
CrossOrigin = "anonymous",
Location = ResourceLocation.Body,
Reload = true
}
(Swiper js installation instructions do not include the integrity)
And I've got this several times, although it's inconsistent and I don't know yet the cause:
Also note that this happened both in an Azure deployment and local environment.
This results in failing to import the module:
Expected Behavior
Steps To Reproduce
Anything else?
@mdmontesinos your Resource declaration does not include the Integrity property, however this is a requirement for many CDNs. This is an example from the Oqtane Theme:
new Resource { ResourceType = ResourceType.Stylesheet, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.3.3/cyborg/bootstrap.min.css",
Integrity = "sha512-M+Wrv9LTvQe81gFD2ZE3xxPTN5V2n1iLCXsldIxXvfs6tP+6VihBCwCMBkkjkQUZVmEHBsowb9Vqsq1et1teEg==",
CrossOrigin = "anonymous" },
@sbwalker Yes, I know. As I mentioned earlier, Swiperjs does not provide an integrity hash in its installation guide, so I assumed it wasn't required and it had something to do with Oqtane js handling.
Are you using WebAssembly? If so, there is some documentation related to this topic:
No, it's static render mode, with server interactivity (although I've gotten the error with all static modules and anonymous user)
I don't think this would be related to Oqtane... Oqtane simply creates the script tag and puts it into the page output - the browser interprets the script tag and does the validation of the remote resource
Thanks for the feedback, I'll keep investigating on my side.
I ended up locking to a specific version of the library (i.e. 14.1.1 instead of generic 14 that could change without me noticing) so that I can manually compute the integrity hash and add it to the resource declaration. The error doesn't seem to appear anymore.
@sbwalker Sorry for reopening the issue, but actually the error has not disappeared. I actually believe it's related to the "Reload" property of the resources and PageScript, because it generates a preload link.
When first loading the page where the script is requested, it works fine and the integrity error does not show up. However, if the first load is in a different page and I then navigate to the page (with enhanced-navigation), the error appears.
Also, I have several resources from cdn with the integrity value, but only some of them fail. And even sometimes one fails and the others not, and another time, they change.
This is an example of a script that fails when enh-navigating:
Resource declaration:
new Resource {
ResourceType = ResourceType.Script,
Url = "https://unpkg.com/imagesloaded@5.0.0/imagesloaded.pkgd.min.js",
CrossOrigin = "anonymous",
Integrity = "sha384-e3sbGkYzJZpi7OdZc2eUoj7saI8K/Qbn+kPTdWyUQloiKIc9HRH4RUWFVxTonzTg",
Location = ResourceLocation.Body,
Reload = true
},
Page output:
<link rel="modulepreload" href="https://unpkg.com/imagesloaded@5.0.0/imagesloaded.pkgd.min.js" integrity="sha384-e3sbGkYzJZpi7OdZc2eUoj7saI8K/Qbn+kPTdWyUQloiKIc9HRH4RUWFVxTonzTg" crossorigin="anonymous">
<page-script src="https://unpkg.com/imagesloaded@5.0.0/imagesloaded.pkgd.min.js"></page-script>
Error:
Failed to find a valid digest in the 'integrity' attribute for resource 'https://unpkg.com/imagesloaded@5.0.0/imagesloaded.pkgd.min.js' with computed SHA-384 integrity 'e3sbGkYzJZpi7OdZc2eUoj7saI8K/Qbn+kPTdWyUQloiKIc9HRH4RUWFVxTonzTg'. The resource has been blocked.
I'm using https://www.srihash.org/ to compute the hash.
Oqtane is specifying the integrity and crossorigin in the exact format documented by Microsoft:
MackinnonBuck/blazor-page-script@44ad683
I think this will need to be escalated to Mackinnon Buck.
One other item to mention is that not all JavaScript libraries need to be reloaded on every page navigation. Generally they only need to be reloaded if they contain "onload" logic. If you look at the Arsha theme it only specifies Reload for the custom scripts which are part of theme (as they use "onload") - whereas all of the other references to JavaScript libraries (ie. Swiper) do not specify Reload.
Usually for me, when I don't specifiy Reload, most libraries won't work properly, and it's also hard to determine when one does theoretically require it.
I'll try to get a minimal repo of the integrity issue for Mackinnon Buck.
page-script was actually made obsolete by the new BlazorJsComponents, which I believe were presented in the TrailBlazor conference.
Oqtane is specifying the integrity and crossorigin in the exact format documented by Microsoft:
MackinnonBuck/blazor-page-script@44ad683
I think this will need to be escalated to Mackinnon Buck.
And this change was never integrated into the main branch and released in his repo, so perhaps it wasn't properly tested, and I have no way to create a minimal repo without using Oqtane.
I wasn't able to reproduce with a regular Blazor app (no interactivity) using Mackinnon Buck's PageScript cloned from the branch that contains the integrity and crossorigin.
Therefore, it seems to be specific to Oqtane in some way. To clarify the issue and reproduce:
- Create a module (not theme) and declare a js resource with integrity and crossorigin properties, and Reload=true (i.e.):
new Resource {
ResourceType = ResourceType.Script,
Url = "https://unpkg.com/imagesloaded@5.0.0/imagesloaded.pkgd.min.js",
CrossOrigin = "anonymous",
Integrity = "sha384-e3sbGkYzJZpi7OdZc2eUoj7saI8K/Qbn+kPTdWyUQloiKIc9HRH4RUWFVxTonzTg",
Location = ResourceLocation.Body,
Reload = true
},
- Add the module in a page that is not the home and not shared in all pages
- Go to Home page and do a hard refresh (to clear cache)
- Navigate to the page that contains the module
- Inspect the console and the error should appear
Failed to find a valid digest in the 'integrity' attribute for resource 'https://unpkg.com/imagesloaded@5.0.0/imagesloaded.pkgd.min.js' with computed SHA-384 integrity 'e3sbGkYzJZpi7OdZc2eUoj7saI8K/Qbn+kPTdWyUQloiKIc9HRH4RUWFVxTonzTg'. The resource has been blocked.
This will only happen when a script is added for the first time after an enhanced-navigation. If you do a hard refresh in the page that contains the module, the error won't be thrown (i.e. theme resources never show this behaviour).
As a current workaround, you must remove both CrossOrigin and Integrity from the resource. If any of them are not empty, page-script will try to add the link with modulepreload and it will fail. Of course, not having a crossorigin and integrity for external cdn resources is a security risk and vulnerability, so this should be fixed.
In my original comment, the resource for swiper contained the CrossOrigin property but not the integrity, which is why it was failing.
new Resource {
ResourceType = ResourceType.Script,
Url = "https://cdn.jsdelivr.net/npm/swiper@11/swiper-element-bundle.min.js",
//CrossOrigin = "anonymous",
Location = ResourceLocation.Body,
Reload = true
}
One other item to mention is that not all JavaScript libraries need to be reloaded on every page navigation. Generally they only need to be reloaded if they contain "onload" logic. If you look at the Arsha theme it only specifies Reload for the custom scripts which are part of theme (as they use "onload") - whereas all of the other references to JavaScript libraries (ie. Swiper) do not specify Reload.
This is only true for resources defined on the theme, as they are always added on the first page load (i.e. bootstrap). However, for resources defined in modules (such as swiper for a oqtane slider module), they might get added for the first time after an enhanced-navigation, which is why they all require the Reload property.
@mdmontesinos indicated that he specified both the integrity and crossorigin properties:
"To clarify the issue and reproduce:
Create a module (not theme) and declare a js resource with integrity and crossorigin properties, and Reload=true (i.e.):
new Resource {
ResourceType = ResourceType.Script,
Url = "https://unpkg.com/imagesloaded@5.0.0/imagesloaded.pkgd.min.js",
CrossOrigin = "anonymous",
Integrity = "sha384-e3sbGkYzJZpi7OdZc2eUoj7saI8K/Qbn+kPTdWyUQloiKIc9HRH4RUWFVxTonzTg",
Location = ResourceLocation.Body,
Reload = true
},
- Add the module in a page that is not the home and not shared in all pages
- Go to Home page and do a hard refresh (to clear cache)
- Navigate to the page that contains the module
- Inspect the console and the error should appear
"
there also have a content in @mdmontesinos 's comment:
"As a current workaround, you must remove both CrossOrigin and Integrity from the resource."
there also have a content in @mdmontesinos 's comment: "As a current workaround, you must remove both CrossOrigin and Integrity from the resource."
This seems to work, but external resources should always include crossOrigin and integrity due to security concerns.
Anyway, I'll try to create a minimal repo for a module that reproduces the issue.
@sbwalker @zyhfish I just created a minimal repo for a module which reproduces the issue.
https://github.com/mdmontesinos/mdmontesinos.Module.Issue4812
Check the browser's console logs. It actually seems you are actually reproducing the error, as the masonry grid is not aligned as it should, it's behaving as a regular grid.
Are you doing a hard-refresh and clearing cache on a different page, and then navigating to the page that contains the module?
If so, then I don't know why it behaves differently for you or how to even debug it on my side.
I followed your steps to repro:
- Create a new empty page in your Oqtane site
- Add a new module instance for Issue4812
- Logout (just in case)
- Open a new incognito browser
- Open the Oqtane home page
- Navigate to the page that contains the module
The steps do not say anything about hard refresh or clearing the cache
Yes, navigating in incognito should reproduce it as well. Then, I don't know what the problem is.
@zyhfish I was not able to reproduce... however if it is a race condition based on JavaScript library dependencies then I would investigate how the JavaScript is being declared and utilized...
I do not understand why the references to masonry.pkgd.min.js and imagesloaded.pkgd.min.js are declared for injection in the Body and have Reload set to True. Reload should only be set to True if the library contains 'onload' events which need to be executed on page navigations. I would try loading these 2 scripts in the Head and setting Reload = false.
not sure why this is using a setTimeout of 500 milliseconds?
@sbwalker The resources are declared for injection in the Body due to performance reasons. If a js script is placed in the Head, it would delay the first render of the page, especially when loading multiple external and "big" resources.
As for the 500 milliseconds setTimeout, it was a way to wait for the external resources to load, as they are required in the custom script. I didn't bother to add more advances ways to achieve this in the minimal repo.
About the Reload property, I wasn't able to use external scripts yet if they are loaded after an enhanced navigation (not in the first page load), if Reload is not true.
@zyhfish The race condition is not the issue I was referring to, it can be solved with a polling and promise mechanism to ensure the Masonry object is defined befored executing the custom script. Again, I didn't want to add more complexity to the minimal repo.
@mdmontesinos I tried out your repro module again... however I made the changes that i described above (ie. I loaded the external scripts in the Head and set Reload to false):
Resources = [
new Resource {
ResourceType = ResourceType.Script,
Url = "https://cdn.jsdelivr.net/npm/masonry-layout@4.2.2/dist/masonry.pkgd.min.js",
CrossOrigin = "anonymous",
Integrity = "sha384-GNFwBvfVxBkLMJpYMOABq3c+d3KnQxudP/mGPkzpZSTYykLBNsZEnG2D9G/X/+7D",
Location = ResourceLocation.Head,
Reload = false
},
new Resource {
ResourceType = ResourceType.Script,
Url = "https://unpkg.com/imagesloaded@5.0.0/imagesloaded.pkgd.min.js",
CrossOrigin = "anonymous",
Integrity = "sha384-e3sbGkYzJZpi7OdZc2eUoj7saI8K/Qbn+kPTdWyUQloiKIc9HRH4RUWFVxTonzTg",
Location = ResourceLocation.Head,
Reload = false
},
new Resource {
ES6Module = true,
ResourceType = ResourceType.Script,
Location = ResourceLocation.Body,
Url = "~/Module.js",
Reload = true,
}
]
And the result seems to work fine:
You can clearly see that the external JavaScript references were included in the head including the integrity and crossorigin attributes... and the module.js was included in the body using the custom page-script element so that it is executed on every page transition.
@sbwalker Yes, the external js scripts are added to the head/body, but they aren't actually loaded if Reload is not true.
I've updated the minimal repo to .NET 9, added a robust way to wait for them load, and disabled Reload. Also, I've updated the README to show how the grid should look if everything is working as intended and a status message (please verify that you get the same result).
If Reload is not true, it only works if the first page load is in the page that contains the module. If the scripts were not loaded on the first hard refresh (Control + F5), and added after an enhanced navigation, they don't work.
Obviously, this is with Static Render Mode and Enhanced Navigation, without interactivity.
I think this thread outlines some of the challenges you are seeing with SSR and JavaScript in Blazor:
I think a lot of people were hopeful that this would be resolved in .NET 9... however it looks like it was punted to .NET 10. So this means developers need to come up with their own custom solutions :(
I have been doing some research related to Blazor Static Rendering and JavaScript. I created a new version of the PageScript custom HTML element which does the following:
- allows for additional attributes to be included on the page-script element
- when page-script is executed it uses the attributes to dynamically inject a script element into the page (and then removes the page-script element from the page)
- uses the onEnhancedLoad Blazor event to load/execute scripts on every Enhanced Navigation
The benefit of this approach:
- supports standard JavaScript libraries**
- does not require scripts to be "modules"
- does not require developers to implement specific events ie. export function onUpdate()
- supports external scripts with integrity and crossorigin attributes in a native way
- supports inline scripts ie. google analytics, etc...
However there are some limitations:
- scripts can only be dynamically injected into the page head - if you try to inject them into the body an exception is raised by blazor.web.js (which seems to be related to the fact that Blazor itself also injects elements such as component state, etc.. into the page body and does not like when the structure of these elements changes). I plan to report this to Microsoft as I believe blazor.web.js should be more resilient.
- **scripts still cannot support onload() behaviors in a standard way (due to Blazor limitations) - scripts are executed only when the Blazor onEnhancedLoad() event fires... which may require some customizations to scripts/libraries which rely on traditional onload() behavior
- does not include any extended support for script dependencies (ie. loading order)
It would be possible to enhance Oqtane so that when a site is using Static Rendering, script elements would be rendered using the page-script approach by default. This would ensure that scripts are always loaded/executed on every Enhanced Navigation. And it would make it easier to integrate JavaScript with custom modules/themes.
Note that the existing "Reload" concept would still be retained for backward compatibility ie. for those scenarios where you want to use JavaScript modules and hook the onUpdate() event. There is some run-time efficiency to this approach so in some cases the development complexity may be worth it.
@sbwalker Thanks for your efforts on improving the js experience in Oqtane!
allows for additional attributes to be included on the page-script element
Does it allow any attribute (even custom ones)? For example, with custom attributes in the script I could achieve the GDPR compliance method I discussed in #4791 (comment).
scripts can only be dynamically injected into the page head
This is a considerable limitation because scripts in the head are a performance bottleneck. Only critical scripts should be included in the head and the rest in the body to improve the loading in slow devices and networks.
**scripts still cannot support onload() behaviors in a standard way (due to Blazor limitations) - scripts are executed only when the Blazor onEnhancedLoad() event fires... which may require some customizations to scripts/libraries which rely on traditional onload() behavior
Well, this is essentially the same behaviour as the current implementation, where you use the onUpdate method of the Reload property to hook the scripts. It's not such a big deal.
does not include any extended support for script dependencies (ie. loading order)
This is also quite problematic, as you need some kind of manual mechanism to ensure that dependencies are loaded before executing custom scripts. For example, https://github.com/mdmontesinos/mdmontesinos.Module.Issue4812/blob/master/Server/wwwroot/Modules/mdmontesinos.Module.Issue4812/Module.js
It would be possible to enhance Oqtane so that when a site is using Static Rendering, script elements would be rendered using the page-script approach by default.
This would be the ideal approach, specially easing the process for new developers that are not familiar with these js limitations.
@mdmontesinos PR #4913 will need some thorough testing. Primarily it is intended to solve the one major problem you identified where scripts which are not part of the initial page load, will not be loaded during subsequent enhanced navigations in Blazor Static Rendering. It solves this problem by using an enhanced version of the custom page-script element. The enhanced version supports dynamic injection of standard scripts (both inline and external libraries). It also supports the original "reload" feature for JavaScript modules where you need to simulate onload behavior in Static Rendering.
I tested this PR with your Issue4812 sample module (using Masonry and ImagesLoaded) and it seems to work properly.
I also tested this PR by including inline scripts in the Head Content field in Page Management ie.
<script>console.log("Visited Page!");</script>
The enhancement does not support custom attributes - that was not part of the problem I was trying to solve. It is also limited to injecting scripts in the head because Blazor throws an exception if scripts are are dynamically injected into the body when there are interactive components on the page (I will follow up with Microsoft about this).
@sbwalker I will start testing the PR.
@mdmontesinos actually you may want to wait a bit longer... there are some issues with the PR which I identified as part of expanded testing. I am hopeful I will be able to resolve them today and if not I may rollback these changes.
@sbwalker Ok then, I will wait further confirmation from your side. Thanks for the heads up!
@mdmontesinos ok I just merged #4917 - you can go ahead and test now
@sbwalker I found an error when trying to load an inline script that contains ':'. For example, structured data for Google Search has a format like this:
<script type="application/ld+json">
{
"@context": "https://schema.org/",
"@type": "Recipe",
"name": "Party Coffee Cake",
"author": {
"@type": "Person",
"name": "Mary Stone"
},
"datePublished": "2018-03-10",
"description": "This coffee cake is awesome and perfect for parties.",
"prepTime": "PT20M"
}
</script>
And it throws this error (both in enhanced load and first load):
VM150:2 Uncaught SyntaxError: Failed to execute 'appendChild' on 'Node': Unexpected token ':'
at Oqtane.Server.60tw9j95pp.lib.module.js:106:23
at new Promise (<anonymous>)
at injectScript (Oqtane.Server.60tw9j95pp.lib.module.js:83:12)
at onEnhancedLoad (Oqtane.Server.60tw9j95pp.lib.module.js:66:17)
at Ri.dispatchEvent (blazor.web.js:1:185152)
at Object.documentUpdated (blazor.web.js:1:185984)
at blazor.web.js:1:176868
at Object.write (blazor.web.js:1:175286)
Also, I added a console log to check the generated scripts, and it's actually breaking some of them.
For example, the structured data script must have a type "application/ld+json", but it removes it. I'm adding it using the AddHeadContent method from the ThemeBase.
Similar thing happens with Google Tag Manager, that should be async but the generated script does not contain the attribute. This script was added in the PageContent of the SiteSettings.
All the original attributes should be preserved when recreating the script from the page-script element.
Additionally, as the order in which the scripts are injected seems to have changed, several scripts that depended on bootstrap don't work (as they load before bootstrap). I would now require custom mechanisms everywhere to ensure that dependencies are already available.
I've also detected that scripts with reload=true that have the onUpdate still do not work when they are first added after an enhanced navigation. According to the new system, this script shouldn't be in the body of the html (it should have been removed), which means it wasn't properly loaded:
@mdmontesinos I fixed the issue where the type attribute was not being preserved for inline scripts (#4920).
In regards to the async attrubute, since PageScript dynamically renders the script tags later in the render life cycle it means the async and defer script attributes are not relevant.
I will investigate the issue related to ordering of the scripts
Are you able to share an example of where the onUpdate is not fired on an enhanced navigation? I have tried a variety of scenarios and I cannot reproduce.
I fixed the issue where the type attribute was not being preserved for inline scripts (#4920).
I can confirm that this fixed the issue and the error described about the structured data disappeared.
Are you able to share an example of where the onUpdate is not fired on an enhanced navigation? I have tried a variety of scenarios and I cannot reproduce.
I actually don't know how to share an example with you. The same happened with the minimal repo I shared previously, I could reproduce the issue but you followed the same steps and it was working fine for you...
EDIT: I don't even know why or how, but it suddenly started working as expected. Scripts with reload are properly loaded and moved to the head and the onUpdate method is executed.
Also, I can confirm that the behaviour I described in this issue seems to be fixed now. I can load the masonry and imagesloaded scripts without reload, and including the integrity and crossorigin attributes.
I also noticed another issue, the SEO meta tags end up in the body instead of the head of the HTML. I just verified and it was working fine before the changes. They are added with AddHeadContent method from the ThemeBase.
Another thing related to the order of scripts, the masonry and imagesloaded scripts are set between the gtag comment and the script.
@mdmontesinos #4921 resolves the issue with meta/link tags being moved to the body. For some reason when Blazor encounters a page-script element in the head it moves the element and all elements beneath it to the body. The PR ensures that page-script elements are rendered at the very end of the head.
The issue with the comment is related to the fact that HeadContent needs to be parsed to identify the scripts so that they can be processed differently than the other content. The parser does not know that the comment belongs to a specific script tag - so it will not relocate the comment. I am not going to fix this as it does not cause any run-time issues.
@sbwalker Meta/links are now properly set in the head.
I am not going to fix this as it does not cause any run-time issues.
Sure, no problem. I just removed the comment so it does not look as weird.
I'm now experiencing some weird behaviour related to bootstrap after the changes, like it's being "loaded" multiple times.
After, each enhanced navigation, when I open the control panel, a new backdrop element is added:
And also in Site Settings, I can't close the accordions (only if I entered the page after an enhanced navigation):
screen-capture.webm
@mdmontesinos thank you for testing.... the original premise for this enhancement was that when using Static Rendering all script tags could be dynamically generated using the page-script custom element. For some scripts this works fine, but for scripts like Bootstrap it does not, as these scripts are intended to only be loaded once.
So I have adjusted the solution as follows (in #4922):
- Script resources that are declared within a Module, are inline scripts (ie. Content != ""), or specify Reload = True are rendered using page-script.
- All other script resources will be rendered as standard script elements in the page (the legacy behavior). This allows script resources declared at the Theme level to be loaded once and shared across pages.
Please test these latest changes and let me know if you encounter any issues.
@sbwalker Thank you for taking the time to resolve the issues I was facing!
I just tested the latest PR and everything seems to be working as expected.
Apart from being able to set the integrity and crossorigin attributes for enhanced security, these changes finally resolve the uncertainties about the "Reload" property.
Feel free to close the issue as resolved if there's nothing else you want me to test.
Also, with the scripts now working like intented, I would like to continue the discussion on #4791, which would require supporting custom attributes.
@mdmontesinos I have spent almost a week now trying to understand the behaviors of scripts in the Blazor SSR "black box". The good news is that I now understand how to inject standard/inline scripts during enhanced navigations so that they are not ignored (and also do not result in exceptions being thrown by Blazor). The bad news is that the current solution is a bit of a Rube Goldberg machine which will be very difficult to maintain going forward. The main reason for this is because current solution was developed using a trial-and-error approach. This was due to the fact that Blazor SSR is uncharted territory and required many "experiments" to understand its internal behavior. In addition, the original decision to enhance the page-script custom element resulted in a variety of technical challenges as well as a complex life cycle to manage and debug.
So I took a step back yesterday to evaluate the current solution and was able to come up with a much simpler approach with much better separation of concerns. Instead of modifying the internals of Mackinnon Buck's page-script custom element, the new solution in #4924 utilizes page-script in the standard way to invoke a custom "reload" script during enhanced navigation. The reload script processes all of the script elements on a page according to some simple business rules:
-
When a script is encountered after an enhanced navigation which was not part of the page previously, Blazor SSR ignores it... so the reload script will replace the script element dynamically with an exact replica which will force the browser to load it.
-
When a script is encountered after an enhanced navigation which was already part of the page previously, it is assumed that it was already loaded so it will be ignored. However, there are some scripts which should be reloaded on every enhanced navigation, so to handle this scenario a data-reload custom attribute can be added to a script, which will result in the script element being replaced dynamically with an exact replica (which will force the browser to reload it). Inline scripts that are specified using Site/Page Content or AddHeadContent() will automatically have the data-reload attribute applied.
This solution solves the original problem of Blazor SSR not loading scripts on enhanced navigations. It supports external and inline scripts. It supports integrity and crossorigin attributes. It loads scripts in the order they are defined in the page. And it still supports the Reload concept which allows you to plugin your own scripts to hook enhanced navigations for custom behaviors.
Please test this latest PR and let me know if you encounter any problems.
@sbwalker Thanks for the efforts made on improving this area.
I have tested the latest PR and everything seems to be working as expected, just like with the previous version but with much more readable and understandble code, so congrats!
@mdmontesinos PR #4927 adds support for type attributes and data-* attributes. I believe this is what you were asking for in the #4791 discussion
@sbwalker Thanks for adding support for type and data-* attributes, it takes us a step closing to achieving GDPR compliance. Also, it's great that you took into account my suggestion of having different classes for stylesheets and resources.
However, I'm encountering some issues with the current implementation.
For example, imagine I have a script that uses the onUpdate so it needs the Reload set to true, but it also sets some cookies or process personal data in some way so it must be blocked until user gives consent. For that, I would need to declare the resource like this:
new Resource {
ResourceType = ResourceType.Script,
Location = ResourceLocation.Body,
Url = "Modules/my-script.js",
Reload = true,
Type = "text/plain",
DataAttributes = new Dictionary<string, string>
{
{ "data-category", "analytics" },
{ "data-service", "My Analytics Service" },
{ "data-type", "module" }
}
}
By setting the type to "text/plain", the script wouldn't normally be executed at first, and with the custom data-* attributes, the cookie consent library can later retrieve it and change its type to module once the category/service is accepted via the consent modal.
However, as it has the Reload set to true, it is rendered directly as a page-script so the type is not applied.
<page-script src="/Modules/my-script.js"></page-script>
Therefore, the execution can't be stopped before the user gives consent (nor afterwards if the user tries to change the consent status).
As for inline scripts declared with AddHeadContent, it kinda works and I get this output, which the consent library is later able to remove the text/plain type when the consent is given.
<script data-reload="true" type="text/plain" data-category="analytics" data-service="ga">
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'ID');
</script>
However, I'm getting the impression that it's being loaded even if the type is text because sometimes there's a network entry to google analytics and also the _ga cookie is set. Is it possible that with the data-reload=true the script's src/inline content is being loaded in the first load no matter the type?
Also, with every enhanced navigation the script is replaced again with its original version (with the type text and data attributes), so the cookie consent tool must again search for all the scripts, remove the tags and reload it, resulting in fetching the script in every navigation, which is a huge degrade in performance. For example with Google Analytics:
@mdmontesinos Oqtane is making an assumption that any inline script rendered into a page (ie. either through the UI as Site/Page Head/Body Content or by calling AddHeadContent() ) is intended to be reloaded. If it did not make this assumption then all inline scripts would be ignored on enhanced navigations (unless an administrator manually added the data-reload custom attribute). If you specify an inline script and you do NOT want it to be reloaded on enhanced navigations, you can specify the data-reload="false" attribute on your script element (ie.<script data-reload="false">console.log("test");</script>
) and this will instruct Oqtane to not reload the script. I believe this is the correct default behavior for inline scripts and it is consistent with the way Oqtane handles inline scripts in Interactive render mode.
In regards to the GDPR enhancement, the goal of #4927 was to provide more granular control over the construction of script elements, by providing the ability to specify the type and data attributes - it does not solve the problem of changing attributes dynamically at run-time. I am not sure if its going to be possible or not, but my suggestion is that you take the same approach that I took with the reload script. Basically you should separate the concerns - allow Oqtane to render the script elements in a standard way (ie. not using page-script) and then create a separate page-script module whose sole responsibility is to modify any scripts on the page during enhanced navigation based on business rules you define. Basically I am saying you should avoid trying to use a page-script to both render a script tag and modify its attributes.
I am going to close this issue as I believe the original issue has now been resolved
@mdmontesinos you may want to take a look at this repo:
https://github.com/devessenceinc/BlazorScriptReload
I was able to make your Masonry example work consistently with some very standard script declarations:
and a much simpler script:
https://github.com/devessenceinc/BlazorScriptReload/blob/main/BlazorScriptReload/wwwroot/gallery.js
@sbwalker Nice, that's a really clean way to handle all types of scripts. I assume it also supports scoped js files (.razor.js), right?
@mdmontesinos I believe scoped JavaScript (ie. JavaScript isolation) is only supported in Interactive Blazor - whereas this "reload" solution is intended for Static Blazor.