Avoiding PWA to reload when using Web Share Target API - javascript

I'm working on a PWA that plays music.
It accepts shared URLs from other android apps via Web Share Target API.
The problem :
Whenever something is shared via the API (which uses GET request)I my PWA is reloded (as expected).
And the already playing Music stops because of that.
Is there any way that page doesn't reload ?
Currently fetching shared params with this code.
const parsedUrl = new URL(window.location);
My PWA is a Single Page application
Thanks

One way to prevent reloading is by setting up a fake path in your Web Application Manifest that solely serves for the purpose of receiving shares (note that this uses HTTP POST and "multipart/form-data" encoding [so later you can extend the app to receive entire files]):
{
"share_target": {
"action": "/receive-shares",
"method": "POST",
"enctype": "multipart/form-data",
"params": {
"title": "name",
"text": "description",
"url": "link"
}
}
}
Then in your service worker's fetch handler, you deal with the incoming shares and redirect the user to the home:
self.addEventListener('fetch', (e) => {
if ((e.request.url.endsWith('/receive-shares')) &&
(e.request.method === 'POST')) {
return e.respondWith((async () => {
// This function is async.
const formData = await fetchEvent.request.formData();
// Do something with the URL…
const url = formData.get('url');
// Store the URL, process it, communicate it to the clients…
// You need to redirect the user somewhere, since the path
// /receive-shares does not actually exist.
return Response.redirect('/', 303);
})())
}
/* Your regular fetch handler */
})

Related

Is it possible to authenticate from SPA with Microsoft Authentication Library but without Redirect Uri?

I'm creating a Javascript single page application. It requires a user to sign in during the implicit flow in order to use Microsoft Graph API later on.
I'm using MSAL.js and trying to adapt snippets from this guide to get auth token from authRedirectCallBack
I realize that in other flows redirect uri (used after authentication) is essential to get the token and proceed with it.
However in my flow I have token processed in the Javascript callback function.
Is it possible to avoid providing redirect uri in App registration with Azure AD and/or during the code execution? I'd like not to bring the user to this redirect uri at all.
At the moment my code looks like this:
var msalConfig = {
auth: {
clientId: 'my-app-id',
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true,
forceRefresh: false
}
};
const loginRequest = {scopes: ['openid', 'profile', 'user.read']};
const msalClient = new Msal.UserAgentApplication(msalConfig);
msalClient.handleRedirectCallback(authRedirectCallBack);
singIn();
async function singIn() {
try {
msalClient.loginPopup(loginRequest).then(function (response) {
if (msalClient.getAccount()) {
console.log('logged');
}
});
} catch (err) {
console.log(err);
}
}
function authRedirectCallBack(err, response) {
if (err) {
console.log(err);
} else {
if (response.tokenType === "access_token") {
console.log('token', response);
}
}
}
A redirect URI is required, as it is the main security mechanism we use to ensure the response is not returned to an unauthorized party, as the Implicit Flow relies on redirecting iframes/popups/windows back to your application with the response in the hash of the url.
Note, that if you use loginPopup/acquireTokenPopup/acquireTokenSilent, the end user will not really "see" this redirect page, as it is visited only briefly in either a hidden iframe (for acquireTokenSilent) or popup window. As soon as MSAL.js sees that the iframe/popup has been redirected back to your application, the response is parsed and the popup/iframe is closed.
We are working on a new version of the library that will switch to the Auth Code Flow w/ PKCE, which will not use hidden iframes, however, it will still use popups/redirects.
Can you further explain why you don't want your users to visit the redirect page?

How to handle images offline in a PWA share target?

I can register my Progressive Web App as a share target for images (supported from Chrome Android 76):
"share_target": {
"action": "/share-target",
"method": "POST",
"enctype": "multipart/form-data",
"params": {
"files": [
{
"name": "myImage",
"accept": ["image/jpeg", "image/png"]
}
]
}
}
I can then intercept attempts to share images to the app in a service worker:
self.addEventListener('fetch', e => {
if (e.request.method === 'POST' && e.request.url.endsWith('/share-target')) {
// todo
}
})
How would I display the shared image in my offline PWA?
There are a few different steps to take here.
I put together a working example at https://web-share-offline.glitch.me/, with the source at https://glitch.com/edit/#!/web-share-offline
Ensure your web app works offline
This is a prerequisite, and I accomplished it by generating a service worker that would precache my HTML, JS, and CSS using Workbox.
The JS that runs when the home page is loaded uses the Cache Storage API to read a list of image URLs that have been cached, to creates <img> elements on the page corresponding to each one.
Create a share target handler that will cache images
I also used Workbox for this, but it's a bit more involved. The salient points are:
Make sure that you intercept POST requests for the configured share target URL.
It's up to you to read in the body of the shared images and to write them to your local cache using the Cache Storage API.
After you save the shared image to cache, it's a good idea to respond to the POST with a HTTP 303 redirected response, so that the browser will display the actual home page for your web app.
Here's the Workbox configuration code that I used to handle this:
const shareTargetHandler = async ({event}) => {
const formData = await event.request.formData();
const cache = await caches.open('images');
await cache.put(
// TODO: Come up with a more meaningful cache key.
`/images/${Date.now()}`,
// TODO: Get more meaningful metadata and use it
// to construct the response.
new Response(formData.get('image'))
);
// After the POST succeeds, redirect to the main page.
return Response.redirect('/', 303);
};
module.exports = {
// ... other Workbox config ...
runtimeCaching: [{
// Create a 'fake' route to handle the incoming POST.
urlPattern: '/share-target',
method: 'POST',
handler: shareTargetHandler,
}, {
// Create a route to serve the cached images.
urlPattern: new RegExp('/images/\\d+'),
handler: 'CacheOnly',
options: {
cacheName: 'images',
},
}],
};

How to cache audio file for Service workers?

I'm writing a web application in HTML/Javacript that records audio and uploads it to a server. Now, I would like to put it also in cache so it's available to service.workers for an offline scenario. What's the best way to do this?
Program flow:
Record audio
Capture data in a Blob
Save data on server
Listen to recorded stuff
If you are online, of course, all works well.
I would like to have the file locally available for listening before it is remotely saved, and backup it on server ASAP.
This is my routine:
function mettiincache(name, myBlob) {
var resp = new Response(myBlob)
var CACHE_NAME = 'window-cache-v1';
caches.open(CACHE_NAME).then(function (cache) {
cache.put(name, resp)
}).catch(function (error) {
ChromeSamples.setStatus(error);
});
}
When I look in Application/cache storage using Chrome DevTools, I find an entry with the correct path/name and content-Type, but with a content-Length of 0 bytes
Note that you might create / use a separate worker 'audioWorker.js' from the SW.js for running the apps audio cache because IMO its easier to test and the SW lifecycle is pretty involved and pretty oriented to its own app cache of 'real' urls used by the app.
Also note an inconsistency with allowable protocols used in the normal Service-Worker implementation that intercepts calls to 'fetch' - blob protocol used by the browser on your audio blobs will be rejected as invalid Urls by the browser.SW implementation. You cannot simply feed your blobs url to the normal SW lifecycle because its URL starts with 'blob://'.
The url from the audioBlob is fine if you choose NOT to use a SW for the cache. However, you might want to suffix it with a mimeType...
url = URL.createObjectURL(audio); // protocol of this is 'blob://'
wUrl = url +"?type=" + {$audio.blob.data.type};
console.log("SW CACH1 " +wUrl);
myCacheWorker.postMessage({ action: 'navigate', url: wUrl });
in the cacheWorker, onMessage , write to the cache:
onmessage = function( e ){
switch( e.data.action ){
case 'navigate':
upcache(e.data.url).then(() => {
postMessage({done: 'done'});
});
break;
}}
//boiler plate cache write below from any example
var upcache = function( url ){
return caches.open($cname)
.then((openCache) => {
return fetch(fetchUrl).then(function(resp) {
if (!resp.ok) {
throw new TypeError('Bad response status');
}
return openCache.put(url, resp);
})
});
}
you can use sql lite to store data in browser , there is a punch of tools that might help you in development to do that ,
i am using this tool my self , https://sqlitebrowser.org/ for debugging, testing and reading data from browsers ,
you can use Data series and publish it to client side as well .
you may refer to this link also reference how to use sql lite in browser , are you storing audio files as binary files? ,
generally , sql lite is good but you have to take care of storing sensitive data without encrypting it other wise it will be compromised also you may use Indexed Database API v 2.0 .
here is a link for more information about this .
https://www.w3.org/TR/IndexedDB/

Handling oauth2 redirect from electron (or other desktop platforms)

This is mostly a lack of understanding of oauth2 and probably not specific to electron, however I'm trying to wrap my head around how someone would handle an oauth2 redirect url from a desktop platform, like electron?
Assuming there is no webservice setup as part of the app, how would a desktop application prompt a user for credentials against a third party oauth2 service, and then authenticate them correctly?
Electron JS runs a browser instance on your localhost. Therefore, you can handle an oauth2 redirect url by supplying a callback url of https:localhost/whatever/path/you/want. Just be sure to white list it on the oauth2 app registration page for whatever service you are using.
Example:
var authWindow = new BrowserWindow({
width: 800,
height: 600,
show: false,
'node-integration': false,
'web-security': false
});
// This is just an example url - follow the guide for whatever service you are using
var authUrl = 'https://SOMEAPI.com/authorize?{client_secret}....'
authWindow.loadURL(authUrl);
authWindow.show();
// 'will-navigate' is an event emitted when the window.location changes
// newUrl should contain the tokens you need
authWindow.webContents.on('will-navigate', function (event, newUrl) {
console.log(newUrl);
// More complex code to handle tokens goes here
});
authWindow.on('closed', function() {
authWindow = null;
});
A lot of inspiration taken from this page: http://manos.im/blog/electron-oauth-with-github/
Thank you for this solution. I also noticed that the navigate events from the webContents are not reliable when no clicks on the browser window triggers the redirection to the application redirect uri. For example Github login page would never trigger this event with the redirect URI if I was already logged in in the browser window. (It was probably using some session storage).
The workaround I found was to use WebRequest instead
const { session } = require('electron');
// my application redirect uri
const redirectUri = 'http://localhost/oauth/redirect'
// Prepare to filter only the callbacks for my redirectUri
const filter = {
urls: [redirectUri + '*']
};
// intercept all the requests for that includes my redirect uri
session.defaultSession.webRequest.onBeforeRequest(filter, function (details, callback) {
const url = details.url;
// process the callback url and get any param you need
// don't forget to let the request proceed
callback({
cancel: false
});
});

Meteor login via Third party library

I'm trying to login to my meteor site via a third party library like this one:
https://gist.github.com/gabrielhpugliese/4188927
In my server.js i have:
Meteor.methods({
facebook_login: function (fbUser, accessToken) {
var options, serviceData, userId;
serviceData = {
id: fbUser.id,
accessToken: accessToken,
email: fbUser.email
};
options = {
profile: {
name: fbUser.name
}
};
userId = Accounts.updateOrCreateUserFromExternalService('facebook', serviceData, options);
return userId;
}, ......
In my client.js I have:
facebookLogin: function () {
if (Meteor.user())
return;
if (!Session.equals("deviceready", true))
return;
if (!Session.equals("meteorLoggingIn", false))
return;
// Do not run if plugin not available
if (typeof window.plugins === 'undefined')
return;
if (typeof window.plugins.facebookConnect === 'undefined')
return;
// After device ready, create a local alias
var facebookConnect = window.plugins.facebookConnect;
console.log('Begin activity');
Session.equals("meteorLoggingIn", true);
Accounts._setLoggingIn(true);
facebookConnect.login({
permissions: ["email", "user_about_me"],
appId: "123456789012345"
}, function (result) {
console.log("FacebookConnect.login:" + JSON.stringify(result));
// Check for cancellation/error
if (result.cancelled || result.error) {
console.log("FacebookConnect.login:failedWithError:" + result.message);
Accounts._setLoggingIn(false);
Session.equals("meteorLoggingIn", false);
return;
}
var access_token = result.accessToken;
Meteor.call('facebook_login', result, access_token, function (error, user) {
Accounts._setLoggingIn(false);
Session.equals("meteorLoggingIn", false);
if (!error) {
var id = Accounts._makeClientLoggedIn(user.id, user.token);
console.log("FacebookConnect.login: Account activated " + JSON.stringify(Meteor.user()));
} else {
// Accounts._makeClientLoggedOut();
}
});
});
}, // login
facebookLogout: function () {
Meteor.logout();
// var facebookConnect = window.plugins.facebookConnect;
// facebookConnect.logout();
},
The third party library (Facebook Android SDK in my case) works fine. My problem is after the "var id = Accounts._makeClientLoggedIn(user.id, user.token);" the Meteor.user() returns Undefined. However If I do a page refresh in the browser works fine and the template renders as a logged in user.
Anyone knows how to fix the 'Undefined' on client ??
PS. On server side the users collection looks fine. The meteor token and everything else are there.
Solved. I had to add : this.setUserId(userId.id);
after userId = Accounts.updateOrCreateUserFromExternalService('facebook', serviceData, options); at server.js
Meteor's client side javascript can't run fibers. Fibers allows synchronous code to be used with javascript since by design js is asynchronous. This means there are callbacks that need to be used to let you know when the task is complete.
From what it looks like Accounts._makeClientLoggedIn doesn't take a callback & unfortunately and doesn't return any data looking at its source. I can't say i've tried this myself because I can't test your code without the android sdk but have you tried using Deps.flush to do a reactive flush?
Also Meteor also has very clean and easy facbeook integration. If you simply add the facebook meteor package
meteor add accounts-facebook
You can get access to a lovely Meteor.loginWithFacebook method that can make everything reactive and your code simpler and really easy. If you need to modify it to use the Android SDK Dialog instead you can easily modify the code out as the code for the module is out there for you to hack up to your spec
Edit: If you're using an external SDK such as the java SDK/cordova plugin
Set your plugin so that it redirects to the following URL (set up for meteor.com hosting):
http://yourmeteorapp.meteor.com/_oauth/facebook?display=touch&scope=your_scope_request_params&state=state&code=yourOAuthCodeFromJava&redirect=YourAPP
So in the querystring we have:
scope= Contains your facebook scope params (for permissions)
code= Your OAuth code from the java sdk
redirect=Where to redirect to after once logged in instead of the window.close
state= A cros site forgery state value, any random value will do
This url is basically used to mimic would what be given to the REDIRECT_URI at : https://developers.facebook.com/docs/reference/dialogs/oauth/
This will redirect to meteor's OAuth helper (at https://github.com/meteor/meteor/blob/master/packages/accounts-oauth-helper/oauth_server.js)
So what would happen is you give the OAuth code from Java to meteor, it fetches the OAuth token and the user's data, then redirect the user to a URL in your app

Categories

Resources