We have gone through from all possible blogs of AMP but couldn't find any way to include custom JS in AMP. This blog(https://www.ampproject.org/docs/guides/pwa-amp/amp-as-pwa#extend-your-amp-pages-via-service-worker) indicates that we can add Custom JS in AMP with the help of Service worker but haven't describe how to do it.
Please let us know How to do it.
Edit:- After adding JS at the run time, it again show the same old error, Please have a look to the image
Note in the mentioned blog post that you can extend your AMP pages
as soon as they’re served from the origin
Having a service worker in itself doesn't exempt you from AMP restrictions. However if you install a service worker in your AMP page, you can then begin to use AMP as a (progressive) web app, with less restrictions. There are multiple patterns you can use - this article covers the major patterns and this Google Codelab shows you how to implement them.
Hope that helps!
EDIT:
Yeah, okay I see what you mean. You could accomplish that by adding this code to the service worker:
self.addEventListener('fetch', e => {
var url = new URL(e.request.url);
if(url.pathname.split('/').pop().endsWith('amp.html')) {
e.respondWith(
fetch(e.request).then(response => {
var init = {
status: 200,
statusText: "OK",
headers: {'Content-Type': 'text/html'}
};
return response.text().then(body => {
body = body.replace('</body>', '<script src="extra.js" async ></script></body>');
return new Response(body, init);
});
})
);
}
});
This will dynamically add the extra.js file to all the *amp.html pages that your app requests. I believe it will still validate, but I haven't tested it.
Note that this will only work when it's served from your origin (as opposed to the AMP Cache), because that's where your service worker has control.
This resource is where I found the code example (it has a soft paywall).
Let me know if it works! :)
#DavidScales's answer is great if you want to specifically include your custom JS through a service worker for some reason. You could also just include your custom JS in a hidden amp-iframe on the page. Note that all of your custom js would have to be changed to reference the parent, so that it can access the AMP page's DOM.
See this thread.
Also see this thread for how to access the AMP page from inside the iframe.
The amp-install-serviceworker component enables service worker installation via same origin or via the Google AMP Cache.
Note : Service Workers are currently available in Chrome, Firefox and
Opera.
How to use amp-install-serviceworker? CLICK HERE
Related
recently I have been implementing my Ionic App with Persona KYC. But I still have a problem with the integration. When I execute the javascript code in Ionic I get the error mentioned in the title. If someone can help me and the community to resolve this would be awesome.
Script:
function openClient(uid) {
const client = new Persona.Client({
templateId: 'itmpl_KxEjwiJXJMntb25dJXdrBnTP',
environmentId: 'env_gQDYrnzBZfXSiPWWdj4VSRGo',
referenceId: uid,
onReady: () => client.open(),
onComplete: ({ inquiryId, status, fields }) => {
console.log(`Completed inquiry ${inquiryId} with status ${status}`);
}
});
}
function cancelClient() { client.cancel(true); }
Typescript method calling the javascript function:
declare var openClient;
async openVerifyIdentityModal() {
if (!this.verifyIdentity) {
await openClient(this.auth.currentUser.uid);
await Preferences.set({ key: "verifyidentity", value: "true" });
}
}
Ed here from Persona. Thanks for sharing this - I asked our dev team to shed some light. Here's their suggestion:
Error: ReferenceError: self is not defined when using next.js
Error: Refused to display 'https://withpersona.com/' in a frame because it set 'X-Frame-Options' to 'deny'
ReferenceError: self is not defined when using next.js
The Persona JS SDK does not support server rendering. Add the following snippet:
TypeScript
import dynamic from "next/dynamic";
export const PersonaInquiry = dynamic(
() => import('persona').then((module) => module.Inquiry),
{ssr: false}
);
Refused to display 'https://withpersona.com/' in a frame because it set 'X-Frame-Options' to 'deny'
What this means
This error message is a result of a security feature that Persona offers when you integrate Persona using Embedded Flow.
Persona lets you specify, via an allowlist, which domains can load the embedded flow. You should specify only the domains where you embed your Persona flow. Potential attackers will then be blocked from embedding and loading your flow on their domain.
If you see this error, it means that the domain the embedded flow in being loaded on is not on the allowlist.
How to fix
If you see this error, go to the Embedded Flow integration page in the Persona Dashboard, and locate "Step 3 Configure allowed domains". Here, you'll see the Domain allowlist.
Ensure that:
The domain from which you are trying to load the Embedded Flow (and where you're seeing the error message) is on the Domain allowlist.
The domain in the Domain allowlist is correctly spelled and properly formatted. Note: a domain name should NOT include the http:// or https:// part of the URL.
If you are testing, please note:
If you want to load an embedded flow on localhost, you must use an embedded flow that points to your Sandbox environment. An embedded flow that points to your Production environment cannot be loaded on localhost.
If you have a more complex setup, please note:
If your embedded flow is loaded on a webpage that is itself loaded as an iframe on another parent webpage, you must specify all parent origins by setting the frameAncestors option in the JS SDK. See Parameters for details.
We also updated our support documentation to provide you with insights as to where to find a solution for this error message:
https://docs.withpersona.com/docs/troubleshooting-common-errors
Please reach out to our team on support#withpersona.com in case the suggested fix doesn't resolve the error.
Thanks,
Ed
I have a heavy JavaScript file on the server (> 3MB). I want to load the page fast and show a loading progress bar to the user. Currently, I am using fetch and WritableStream to download the data and to track the download progress as:
let resource = await fetch('heavy_file.js')
resource.clone().body.pipeTo(new WritableStream({
write(t) { on_receive(t.length) }
}))
And then I am using the Function to evaluate it. This has several problems. How can I:
Load the script preferably using fetch (I'm using the same method to load WASM files, I want to track their download progress as well, and the WebAssembly.compileStreaming API requires the usage of fetch).
Track the download progress in a way that would work across modern browsers nowadays.
Be able to use this solution without enabling script-src 'unsafe-eval' in Content-Security-Policy?
PS.
Of course, currently, we need to use script-src 'wasm-eval' in Chrome when loading WASM files until the bug is fixed.
Option 1
If you can calculate the hash of the script you're loading in advance (if it's not something generated dynamically), then a simple option to avoid
enabling script-src 'unsafe-eval' in Content-Security-Policy
would be to add only a hash of that specific script into the CSP header - this will still ensure that you're not executing any untrusted code, while allowing you to load and execute script manually.
MDN has some more examples of implementing such CSP policies here.
As for loading itself, you have two different paths from here:
Option 1.1
Combine the hash with a CSP3 policy unsafe-hashes which will allow you to keep using Function or eval like you currently, while still limiting code only to trusted.
For example, if you have a script like
alert('Hello, world.');
then your CSP header should contain
Content-Security-Policy: script-src 'unsafe-hashes' 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='
Unfortunately, CSP level 3, or, at least, this option is supported only in Chromium at the moment of writing.
Option 1.2
Instead of using Function or eval, you can dynamically create a script tag from JavaScript, populate its textContent with your response content and inject it into the DOM:
let resource = await fetch('heavy_file.js')
resource.clone().body.pipeTo(new WritableStream({
write(t) { on_receive(t.length) }
}));
resource.text().then(res => {
let s = document.createElement('script');
s.textContent = res;
document.head.appendChild(s);
});
In this case you only need to add the hash of the script to the CSP and it will work across all browsers:
Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='
Option 2
If you do want to support dynamically generated scripts, then your only option might be to move your progress-tracking code to a Service Worker, and then use Client.postMessage to communicate progress to a script on the page.
This will work for any content served from your origin, but only once Service Worker is installed - usually on a subsequent page load, and so might not help you if the large script you're loading is part of the page user visits first on your website.
Content Script can be injected programatically or permanently by declaring in Extension manifest file. Programatic injection require host permission, which is generally grant by browser or page action.
In my use case, I want to inject gmail, outlook.com and yahoo mail web site without user action. I can do by declaring all of them manifest, but by doing so require all data access to those account. Some use may want to grant only outlook.com, but not gmail. Programatic injection does not work because I need to know when to inject. Using tabs permission is also require another permission.
Is there any good way to optionally inject web site?
You cannot run code on a site without the appropriate permissions. Fortunately, you can add the host permissions to optional_permissions in the manifest file to declare them optional and still allow the extension to use them.
In response to a user gesture, you can use chrome.permission.request to request additional permissions. This API can only be used in extension pages (background page, popup page, options page, ...). As of Chrome 36.0.1957.0, the required user gesture also carries over from content scripts, so if you want to, you could add a click event listener from a content script and use chrome.runtime.sendMessage to send the request to the background page, which in turn calls chrome.permissions.request.
Optional code execution in tabs
After obtaining the host permissions (optional or mandatory), you have to somehow inject the content script (or CSS style) in the matching pages. There are a few options, in order of my preference:
Use the chrome.declarativeContent.RequestContentScript action to insert a content script in the page. Read the documentation if you want to learn how to use this API.
Use the webNavigation API (e.g. chrome.webNavigation.onCommitted) to detect when the user has navigated to the page, then use chrome.tabs.executeScript to insert the content script in the tab (or chrome.tabs.insertCSS to insert styles).
Use the tabs API (chrome.tabs.onUpdated) to detect that a page might have changed, and insert a content script in the page using chrome.tabs.executeScript.
I strongly recommend option 1, because it was specifically designed for this use case. Note: This API was added in Chrome 38, but only worked with optional permissions since Chrome 39. Despite the "WARNING: This action is still experimental and is not supported on stable builds of Chrome." in the documentation, the API is actually supported on stable. Initially the idea was to wait for a review before publishing the API on stable, but that review never came and so now this API has been working fine for almost two years.
The second and third options are similar. The difference between the two is that using the webNavigation API adds an additional permission warning ("Read your browsing history"). For this warning, you get an API that can efficiently filter the navigations, so the number of chrome.tabs.executeScript calls can be minimized.
If you don't want to put this extra permission warning in your permission dialog, then you could blindly try to inject on every tab. If your extension has the permission, then the injection will succeed. Otherwise, it fails. This doesn't sound very efficient, and it is not... ...on the bright side, this method does not require any additional permissions.
By using either of the latter two methods, your content script must be designed in such a way that it can handle multiple insertions (e.g. with a guard). Inserting in frames is also supported (allFrames:true), but only if your extension is allowed to access the tab's URL (or the frame's URL if frameId is set).
I advise against using declarativeContent APIs because they're deprecated and buggy with CSS, as described by the last comment on https://bugs.chromium.org/p/chromium/issues/detail?id=708115.
Use the new content script registration APIs instead. Here's what you need, in two parts:
Programmatic script injection
There's a new contentScripts.register() API which can programmatically register content scripts and they'll be loaded exactly like content_scripts defined in the manifest:
browser.contentScripts.register({
matches: ['https://your-dynamic-domain.example.com/*'],
js: [{file: 'content.js'}]
});
This API is only available in Firefox but there's a Chrome polyfill you can use. If you're using Manifest v3, there's the native chrome.scripting.registerContentScript which does the same thing but slightly differently.
Acquiring new permissions
By using chrome.permissions.request you can add new domains on which you can inject content scripts. An example would be:
// In a content script or options page
document.querySelector('button').addEventListener('click', () => {
chrome.permissions.request({
origins: ['https://your-dynamic-domain.example.com/*']
}, granted => {
if (granted) {
/* Use contentScripts.register */
}
});
});
And you'll have to add optional_permissions in your manifest.json to allow new origins to be requested:
{
"optional_permissions": [
"*://*/*"
]
}
In Manifest v3 this property was renamed to optional_host_permissions.
I also wrote some tools to further simplify this for you and for the end user, such as
webext-domain-permission-toggle and webext-dynamic-content-scripts. They will automatically register your scripts in the next browser launches and allow the user the remove the new permissions and scripts.
Since the existing answer is now a few years old, optional injection is now much easier and is described here. It says that to inject a new file conditionally, you can use the following code:
// The lines I have commented are in the documentation, but the uncommented
// lines are the important part
//chrome.runtime.onMessage.addListener((message, callback) => {
// if (message == “runContentScript”){
chrome.tabs.executeScript({
file: 'contentScript.js'
});
// }
//});
You will need the Active Tab Permission to do this.
I haven't been able to get something like this to work:
var myWorker = new Worker("http://example.com/js/worker.js");
In my Firebug console, I get an error like this:
Failed to load script:
http://example.com/js/worker.js
(nsresult = 0x805303f4)
Every example of web worker usage I've seen loads a script from a relative path. I tried something like this, and it works just fine:
var myWorker = new Worker("worker.js");
But what if I need to load a worker script that's not at a relative location? I've googled extensively, and I haven't seen this issue addressed anywhere.
I should add that I'm attempting to do this in Firefox 3.5.
For those that don't know, here is the spec for Web Worker:
http://www.whatwg.org/specs/web-workers/current-work/
And a post by John Resig:
http://ejohn.org/blog/web-workers/
Javascript, generally, can't access anything outside of the url that the javascript file came from.
I believe that is what this part of the spec means, from: http://www.w3.org/TR/workers/
4.2 Base URLs and origins of workers
Both the origin and effective script origin of scripts running in workers are the origin of the absolute URL given in that the worker's location attribute represents.
This post has a statement about what error should be thrown in your situation:
http://canvex.lazyilluminati.com/misc/cgi/issues.cgi/message/%3Cop.u0ppu4lpidj3kv#zcorpandell.linkoping.osa%3E
According to the Web Worker draft specification, workers must be hosted at the same domain as the "first script", that is, the script that is creating the worker. The URL of the first script is what the worker URL is resolved against.
Not to mention...
Just about anytime you have a Cross-Origin Restriction Policy, there's no counterpoise to the file system (file://path/to/file.ext) - Meaning, the file protocol triggers handling for this policy.
This goes for "dirty images" in the Canvas API as well.
Hope this helps =]
Hey everyone, I'm working on a widget for Apple's Dashboard and I've run into a problem while trying to get data from my server using jquery's ajax function. Here's my javascript code:
$.getJSON("http://example.com/getData.php?act=data",function(json) {
$("#devMessage").html(json.message)
if(json.version != version) {
$("#latestVersion").css("color","red")
}
$("#latestVersion").html(json.version)
})
And the server responds with this json:
{"message":"Hello World","version":"1.0"}
For some reason though, when I run this the fields on the widget don't change. From debugging, I've learned that the widget doesn't even make the request to the server, so it makes me think that Apple has some kind of external URL block in place. I know this can't be true though, because many widgets phone home to check for updates.
Does anyone have any ideas as to what could be wrong?
EDIT: Also, this code works perfectly fine in Safari.
As requested by Luca, here's the PHP and Javascript code that's running right now:
PHP:
echo $_GET["callback"].'({"message":"Hello World","version":"1.0"});';
Javascript:
function showBack(event)
{
var front = document.getElementById("front");
var back = document.getElementById("back");
if (window.widget) {
widget.prepareForTransition("ToBack");
}
front.style.display = "none";
back.style.display = "block";
stopTime();
if (window.widget) {
setTimeout('widget.performTransition();', 0);
}
$.getJSON('http://nakedsteve.com/data/the-button.php?callback=?',function(json) {
$("#devMessage").html(json.message)
if(json.version != version) {
$("#latestVersion").css("color","red")
}
$("#latestVersion").html(json.version)
})
}
In Dashcode click Widget Attributes then Allow Network Access make sure that option is checked. I've built something that simply refused to work, and this was the solution.
Cross-domain Ajax requests ( Using the XMLHttpRequest / ActiveX object ) are not allowed in the current standard, as per the W3C spec:
This specification does not include
the following features which are being
considered for a future version of
this specification:
Cross-site XMLHttpRequest;
However there's 1 technique of doing ajax requests cross-domain, JSONP, by including a script tag on the page, and with a little server configuration.
jQuery supports this, but instead of responding on your server with this
{"message":"Hello World","version":"1.0"}
you'll want to respond with this:
myCallback({"message":"Hello World","version":"1.0"});
myCallback must be the value in the "callback" parameter you passed in the $.getJSON() function. So if I was using PHP, this would work:
echo $_GET["callback"].'({"message":"Hello World","version":"1.0"});';
Apple has some kind of external URL block in place.
In your Info.plist you need to have the key AllowNetworkAccess set to true.
<key>allowNetworkAccess</key>
<true/>
Your code works in Safari because it is not constrained in the dashboard sever and it is not standards complient in that it DOES allow cross site AJAX. FF IS standards complient in that it DOES NOT allow cross site ajax.
If you are creating a dashboard widget, why don't you use the XMLHttpRequest Setup function in the code library of DashCode. Apple built these in so you don't need to install 3rd party JS libraries. I'm not sure about JSON support but perhaps starting here will lead you in a better direction.
So another solution is to create your own server side web service where you can control the CORS of, the users web browser can't access another site, but if you wrap that other site in your own web service (on the same domain) then it does not cause an issue.
Interesting that it works in Safari. As far as I know to do x-domain ajax requests you need to use the jsonp dataType.
http://docs.jquery.com/Ajax/jQuery.getJSON
http://bob.pythonmac.org/archives/2005/12/05/remote-json-jsonp/
Basically you need to add callback=? to your query string and jquery will automatically replace it with the correct method eg:
$.getJSON("http://example.com/getData.php?act=data&callback=?",function(){ ... });
EDIT: put the callback=? bit at the end of the query string just to be safe.