Detect webrequest coming from PWA on the server side - progressive-web-apps

I'm adding PWA support to a web app. I want to add a back-button to the interface for iOS when the app is used as a PWA but not when used in the old fashioned way in Safari (or another browser).
I know I can detect this via JS - so I could just show/hide the back-button at runtime, but I would like to know if there is any way to detect in on the server-side already (apache/php). I guess there will be more use-cases coming up to serve slightly different content.
I guess I could set a cookie, but I want to make sure there is nothing more easy/obvious to use, some new header for example.

Found one more way in the meantime to do it, apart from cookies. Sending a custom http header via a service worker.
app.js
navigator.serviceWorker.register('/sw.js').then(registration => {
if(registration.active) {
const sw = registration.active;
sw.postMessage({
'is_pwa': is_pwa()
});
}
});
sw.js
var myHeader = new Headers(event.request.headers);
if (is_pwa) {
myHeader.append('ISPWA', is_pwa ? is_pwa : "");
}
var config = {
headers: myHeader
};
if(navigator.onLine !== false) {
event.respondWith(fetch(request).catch(function(error) {
fetch(request, config).catch(function() {
[...]
php
var_dump($_SERVER['HTTP_ISPWA']);

Related

How can I handle Media sharing in my PWA app when sharing media from another app into my app?

I am working on a Progressive web app that works on both web version and mobile version. I am sharing media files with others apps but when I share media from another app (Or any other social like Facebook, Messenger, LinkedIn) into my app after selecting my app it just opens up and turns into a black screen. I need help with how I can forward the screen into my share list screen and the data which is shared by another app how can I access them. Any Help?
Double check your method is "POST" and that enctype is present. enctype must be "multipart/form-data", and a files entry must be added in your manifest where you specify your share_target. files is an array that specifies the types of files your app accepts.
more info from: https://web.dev/web-share-target/
The foreground page cannot process this data directly. Since the page sees the data as a request, the page passes it to the service worker, where you can intercept it with a fetch event listener. From here, you can pass the data back to the foreground page using postMessage() or pass it on to the server:
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// If this is an incoming POST request for the
// registered "action" URL, respond to it.
if (event.request.method === 'POST' &&
url.pathname === '/bookmark') {
event.respondWith((async () => {
const formData = await event.request.formData();
const link = formData.get('link') || '';
const responseUrl = await saveBookmark(link);
return Response.redirect(responseUrl, 303);
})());
}
});

Is it possible to display data from SeatGeek API call on my Squarespace site?

Is it possible to connect to the SeatGeek API to display local event data on a Squarespace site?
The Squarespace API docs all seem directed towards commerce-related goals.
I am familiar with how to connect to the SeatGeek API in the context of a mobile application. But I don't know whether connecting to APIs (other than those listed for commerce) from within a Squarespace is doable.
SeatGeek would be an unofficial integration . I've posted on the squarespace forums with no response, so asking here to see if anyone out there knows about it.
Thanks very much for any help!
Squarespace websites above the "Personal" plan tier support the addition of custom JavaScript via Code Blocks and Code Injection.
Therefore, if SeatGeek supports using their API via JavaScript (and it appears that they do), then you can obtain the data from within your Squarespace website.
Where within your site the code is added and what initialization methods are used will vary on a case-by-case basis. For example, factors include: whether you are using Squarespace 7.0 or 7.1 and whether the template you're using supports AJAX Loading and has it enabled.
However, regardless of where the code is added and the initialization methods used, it looks to me, based on what I see here, that obtaining data from SeatGeek via JavaScript is possible. (Select "JavaScript > XMLHttpRequest" or "JavaScript > Fetch" from the upper-right "Code Snippet" panel where it says "(Node.js) Unirest" by default):
var data = null;
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.addEventListener("readystatechange", function () {
if (this.readyState === this.DONE) {
console.log(this.responseText);
}
});
xhr.open("GET", "https://seatgeek-seatgeekcom.p.rapidapi.com/events");
xhr.setRequestHeader("x-rapidapi-host", "seatgeek-seatgeekcom.p.rapidapi.com");
xhr.setRequestHeader("x-rapidapi-key", "SIGN-UP-FOR-KEY");
xhr.send(data);
Or, via fetch:
fetch("https://seatgeek-seatgeekcom.p.rapidapi.com/events", {
"method": "GET",
"headers": {
"x-rapidapi-host": "seatgeek-seatgeekcom.p.rapidapi.com",
"x-rapidapi-key": "SIGN-UP-FOR-KEY"
}
})
.then(response => {
console.log(response);
})
.catch(err => {
console.log(err);
});
While it varies on a case-by-case basis, in most cases you'll want to use the sitewide code injection area vs. code blocks or page-level code injection. Then, on Squarespace 7.0 sites, you'll want to wrap your code in:
window.Squarespace.onInitialize(Y, function() {
// do stuff here
});
For Squarespace 7.1 sites on the other hand, one would usually wrap the code in:
document.addEventListener('DOMContentLoaded', function() {
// do stuff here
}, false);
Finally, you'll need to think about how you're outputting the data. You could either add HTML markup via a Code Block in the body of the target page, or add the markup to the page as part of your JavaScript.

Redirect URL from local filesystem to internet with browser WebExtension

To summarize the problem, I have several PDF files on my computer that contain links to other pages. Those links, however, direct you to the local filesystem instead of the internet (i.e. clicking the link opens the browser and takes you to file:///page instead of http://domain/page).
Getting these files modified to include the full URL is not an option. My question is almost identical to the question I asked a year and a half ago. The difference is that I need to port my existing extension (made with the Firefox SDK) to the new WebExtensions API for Firefox (same as Chrome extensions).
There are methods available for redirection, such as
browser.webRequest.onBeforeRequest.addListener(
redirect,
{urls:[pattern]},
["blocking"]
);
but that only accepts http:// and https:// URL patterns.
I am currently trying to use the following code:
var id;
browser.tabs.onCreated.addListener( details => id = details.id )
browser.tabs.onUpdated.addListener( (tabId, changeInfo, tab) => {
var url = changeInfo.url;
if (tabId == id && url.includes("file:///")) {
url = url.replace("file:///page", http://domain/page");
browser.tabs.update(
id,
{ url: url }
);
}
});
Unfortunately, I have the same fundamental issue as with my original problem, resulting in the onUpdated listener not firing (or if it does fire, it's not because of a URL change). But regardless of the listener used (e.g. onCreated, onActivated, etc.), I get about:blank for the URL.
I have tried injecting code to change the value of the address bar, but that doesn't seem to work either:
browser.tabs.executeScript( {
code: "window.location.href = window.location.href.replace('file:///', 'http://domain/')"
});
Thanks for any help!
redirectUrl : chrome.extension.getURL("hello.html")
Worked for me

FF Addon SDK page mod script to content script communication not working

For starters, I've been trying to allow communication from a page script to a content script. If the docs are accurate, this should be easy. Here's what I'm doing, I believe fully in accordance with https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/guides/content-scripts/communicating-with-other-scripts.html#Using%20the%20DOM%20postMessage%20API :
And here's my live test case:
main.js:
exports.main = function() {
var data = require('sdk/self').data,
pageMod = require('sdk/page-mod');
pageMod.PageMod({
include: '*',
contentScriptFile: data.url('postMessageRelay.js'),
});
};
postMessageRelay.js
// Trying with window.addEventListener also doesn't work
document.defaultView.addEventListener('message', function (e) { // e.data, e.origin
console.log('would be good if it got here:'+e.data);
});
console.log('it gets here at least');
And the JavaScript within a regular HTML file (on a normal remote server, not file or localhost):
try {
window.postMessage('webappfind', window.location.href);
}
catch(e) {
alert(e);
}
This really looks like either a full-blown bug for this functionality or a problem with the docs... I had similar problems trying to communicate via custom events so going a little bananas...
Answered in Bug 910972, but leaving it here to for future visitors of SO:
The issue was with the page immediately firing postMessage in the head tag, so the page-mod script isn't even yet attached to the page to listen to the message event. The communication back and forth between page and content scripts as in this example works as long as this timing is taken into consideration

Best practice for dual-use iFrame + External authentication for Facebook enabled app

Okay, if cookies are a no-no, then I need a little guidance as to the best way to implement the application(s) that I'm creating.
The scenario is that I'm trying to create a single Asp.Net MVC application that can authenticate a user regardless of whether the user visits a site directly or via an iFrame in Facebook. There are separate actions (in separate controllers, actually) for getting INTO the app depending on whether the user enters via Facebook or not, but there are also places in the Facebook app where I'm opening up a new window to "extended" functionality in other areas of the application that can't really work well within the iFrame. It is supposed to transition seamlessly. It's currently working quite well using cookies, but I've from multiple sources that this is not a good thing for iFrame apps. However, I'm not sure exactly what this means.
Without cookies, can you still somehow get server-side access to the authentication token? If not, then what is the "right" way to handle this. Do I need to resort to manually parsing the token using the JS API and sending an AJAX notification to the server of the fact that the user is authenticated and create a forms auth token? Will the CanvasAuthorize attribute work without cookies? Right now I have added code to the FormsAuthentication_OnAuthenticate event in Global.asax to create the forms auth token if the user is logged in via Facebook (and properly associated with a valid user in the external app) as follows:
protected void FormsAuthentication_OnAuthenticate(Object sender, FormsAuthenticationEventArgs args)
{
if (FormsAuthentication.CookiesSupported)
{
if (Request.Cookies[FormsAuthentication.FormsCookieName] == null)
{
// Attempt to authenticate using Facebook
try
{
FacebookApp fbApp = new FacebookApp();
if (fbApp.Session != null)
{
dynamic me = fbApp.Get("me");
String fbID = "" + me.id;
MembershipUser mUser = AppMembershipProvider.GetUserByFacebookID(fbID);
if (mUser != null)
{
FormsAuthentication.SetAuthCookie(mUser.UserName, false);
AppMembershipProvider.UpdateLastLogin(mUser.UserName);
Session["FacebookLogin"] = true;
}
}
}
catch (Exception e)
{
Elmah.ErrorSignal.FromCurrentContext().Raise(e);
}
}
}
else
{
throw new HttpException("Cookieless Forms Authentication is not " +
"supported for this application.");
}
}
Will I need to change this?
Sorry if this is basic knowledge, but I'm confused as to how best to implement this. Thanks!
First, let me address the issue with the cookies. So, when I say to not use cookies in iFrames I am saying that for a couple reasons. First in IE, there are some security issues. You need to add the following header to your app to make cookies work correctly inside iframes:
P3P: CP="CAO PSA OUR"
The second big issue with cookies in iframe apps is Safari. Due to security settings in Safari, cookies cannot be created by iframes. As such, you will not be able to rely on cookies for authentication inside of iframes.
Give that you are using the app inside and outside of the iframe, you should have cookie support turned on. However, your app must be designed in a way that will work around the iframe issues. That is going to be the hard part.
The most reliable authentication inside iframe apps is the signed request method. What happens is facebook will append a query parameter to your url when the url is rendered inside the iframe. This query parameter contains the user's session. The Facebook C# SDK handles reading this for you, so you dont need to parse it etc. But you need to be aware that it is there. If you view the incoming request url of your iframe app in facebook you will see something like http://www.mysite.com/page/?signed_request={blahblahblah}.
So the key is that you need to make sure that if you are in the iframe you keep that ?signed_request value on the url.
You can do this several ways. First, you can use the CanvasRedirect methods. These are extension methods on System.Web.Mvc.Controller in the Facebook.Web.Mvc namespace. The canvas redirect uses javascript to redirect your page in the top url. This way Facebook is actually handling the redirects and will always add the signed_request to your iframe url. The problem for you is that this method of redirecting will only work in the iframe, not outside.
The second way would be to manually add the ?signed_request to the url when you redirect. You would do something like:
public ActionResult Something() {
return RedirectToAction("something", new { signed_request = Request.Querystring["signed_requets"]);
}
There are other ways also, like storing data in the session or something, but I wouldn't recommend going down that path.
What you are doing is definitely an advanced senario, but hopefully the above will help you get going in the right direction. Feel free to contact me directly if you have any questions. nathan#ntotten.com or #ntotten on twitter.
I am in a similar situation to you. What I do to handle the various situations that can arise is:
Enable cookies in both the C# and
JavaScript SDK.
Create a custom actionfilter that
inherits from
FacebookAuthorizeAttribute and
overrides the
HandleUnauthorizedRequest method to
redirect to either a connect
authorization page or an action
decorated with the
CanvasAuthorizeAttribute.
Pass either the signed_request
(canvas app) or auth_token (connect
app) as a querystring parameter to
everything.
Check for null sessions and oauth
tokens that don't match what has been
passed in the querystring.
The main point is to ensure that both the session and oauth tokens are valid. When inside Facebook the signed_request will ensure this is true. By passing the token from your connect auth page you can ensure you have a valid token to inject into the FacebookApp constructor.
public class FbAuthenticateAttribute : FacebookAuthorizeAttribute
{
private FacebookApp _fbApp;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
var accessToken = filterContext.HttpContext.Request.Params["access_token"];
if (FacebookApp.AccessToken != accessToken && !string.IsNullOrEmpty(accessToken))
{
_fbApp = new FacebookApp(accessToken);
}
else
{
_fbApp = FacebookApp;
}
filterContext.Controller.ViewBag.Context = GetContext().ToString();
filterContext.RequestContext.HttpContext.Response.AppendHeader("p3p", "CP=\"CAO PSA OUR\"");
try
{
dynamic user = _fbApp.Get("me");
var signedRequest = filterContext.HttpContext.Request.Params["signed_request"];
filterContext.Controller.ViewBag.QueryString = string.IsNullOrEmpty(signedRequest)
? "?access_token=" + _fbApp.AccessToken
: "?signed_request=" + signedRequest;
}
catch (Exception ex)
{
string url = GetRedirectUrl(filterContext);
filterContext.Result = new RedirectResult(url);
}
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
string url = GetRedirectUrl(filterContext);
filterContext.Result = new RedirectResult(url);
}
private string GetRedirectUrl(ControllerContext filterContext)
{
return new UrlHelper(filterContext.RequestContext).Action(GetRedirectAction(GetContext()), "Authentication");
}
private Enums.AppContext GetContext()
{
//Note: can't rely on this alone - find something more robust
return FacebookApp.SignedRequest == null ? Enums.AppContext.FBWeb : Enums.AppContext.FBApp;
}
private string GetRedirectAction(Enums.AppContext context)
{
return context == Enums.AppContext.FBWeb ? "ConnectAuthenticate" : "Authenticate";
}
}
It could definitely do with a refactor and still has problems but is the best solution I have found so far.