DJANGO PWA Install to home screen not working - django

I am trying to follow the instructions on this site (https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Add_to_home_screen) with my DJANGO app to make a PWA "Install to home screen' button.
I have SSL installed on the site, and it seems to me that the service worker is installed properly, I got the "Service Worker Registered" message back. However when I click the button nothing happens, the + sign does not appear in the URL bar as it should.
I have no idea what is causing the error, as there is no clear sign of anything not working properly.
My index.html:
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>A2HS demo</title>
<link href="{% static 'css/index.css' %}" rel="stylesheet">
<script src="{% static 'js/index.js' %}" defer></script>
<link rel="manifest" href="{% static 'manifest.json' %}">
</head>
<body>
<button class="add-button">Add to home screen</button>
</body>
</html>
My manifest.json:
{
"short_name": "Test site",
"name": "Test site",
"theme_color": "#062440",
"background_color": "#F7F8F9",
"display": "fullscreen",
"icons": [
{
"src": "assets/logo.png",
"type": "image/png",
"sizes": "192x192"
}
],
"start_url": "/index.html"
}
My index.js
// Register service worker to control making site work offline
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('/static/js/service-worker.js')
.then(() => { console.log('Service Worker Registered'); });
}
// Code to handle install prompt on desktop
let deferredPrompt;
const addBtn = document.querySelector('.add-button');
addBtn.style.display = 'none';
window.addEventListener('beforeinstallprompt', (e) => {
// Prevent Chrome 67 and earlier from automatically showing the prompt
e.preventDefault();
// Stash the event so it can be triggered later.
deferredPrompt = e;
// Update UI to notify the user they can add to home screen
addBtn.style.display = 'block';
addBtn.addEventListener('click', () => {
// hide our user interface that shows our A2HS button
addBtn.style.display = 'none';
// Show the prompt
deferredPrompt.prompt();
// Wait for the user to respond to the prompt
deferredPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
console.log('User accepted the A2HS prompt');
} else {
console.log('User dismissed the A2HS prompt');
}
deferredPrompt = null;
});
});
});
And my service-worker.js:
self.addEventListener('install', (e) => {
e.waitUntil(
caches.open('fox-store').then((cache) => cache.addAll([
'/index.html',
'/static/css/index.css',
'/static/js/index.js',
])),
);
});
self.addEventListener('fetch', (e) => {
console.log(e.request.url);
e.respondWith(
caches.match(e.request).then((response) => response || fetch(e.request)),
);
});

Related

How can I embed an Autodesk Forge Viewer to a Flask App without CORS errors?

I followed the Developer's Guide (step 1-3) to create an html file to view my BIM model - this worked fine.
But if I put the same code into a flask app I am not able to view the BIM model. I looked at the requests and found a couple of CORS errors while loading the document with Autodesk.Viewing.Document.load:
If I hover over CORS error I got a small popup telling me: "Cross origin resource sharing error: HeaderDisallowedByPreflightResponse".
I tried to change the callback url of my forge app to http://localhost:5001 and http://localhost:5001/* but this had no impact.
How can I embed an Autodesk Forge Viewer to a Flask App without CORS errors?
Why are there CORS errors on localhost but not on file:///.../index.html?
update:
Here is my html file - which works (I removed secrets here). I basically just put the same code into a flask app...
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=no"/>
<meta charset="utf-8">
<title>Axpo BIM Viewer</title>
<link rel="stylesheet" href="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.min.css"
type="text/css">
<script src="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.js"></script>
<style>
body {
margin: 0;
}
#forgeViewer {
width: 100%;
height: 800px;
margin: 0;
background-color: #F0F8FF;
}
#forgeViewer > div {
height: 800px !important;
}
</style>
<script>
$(() => {
let client_id = '__client_id__'
let client_secret = '__client_secret__'
let documentId = 'urn:__documentId__'
let htmlDiv = document.getElementById('forgeViewer');
let viewer = new Autodesk.Viewing.GuiViewer3D(htmlDiv);
get_an_account(client_id, client_secret).then(function (response) {
initialize_viewer(viewer, response.access_token)
load_document(viewer, documentId)
});
});
async function get_an_account(client_id, client_secret) {
var request = {
"url": 'https://developer.api.autodesk.com/authentication/v1/authenticate',
"method": "POST",
"timeout": 0,
"headers": {
'Content-Type': 'application/x-www-form-urlencoded'
},
"data": {
client_id: client_id,
client_secret: client_secret,
grant_type: 'client_credentials',
scope: 'code:all data:write data:read bucket:create bucket:delete bucket:read'
}
};
return $.ajax(request);
}
function initialize_viewer(viewer, token) {
var options = {
env: 'AutodeskProduction2',
api: 'streamingV2', // for models uploaded to EMEA change this option to 'streamingV2_EU'
getAccessToken: function (onTokenReady) {
var timeInSeconds = 3600; // Use value provided by Forge Authentication (OAuth) API
onTokenReady(token, timeInSeconds);
}
};
Autodesk.Viewing.Initializer(options, () => {
var startedCode = viewer.start();
if (startedCode > 0) {
console.error('Failed to create a Viewer: WebGL not supported.');
return;
}
console.log('Initialization complete, loading a model next...');
});
}
function load_document(viewer, documentId) {
Autodesk.Viewing.Document.load(documentId, (viewerDocument) => {
var defaultModel = viewerDocument.getRoot().getDefaultGeometry();
viewer.loadDocumentNode(viewerDocument, defaultModel);
}, () => {
console.error('Failed fetching Forge manifest');
});
}
</script>
</head>
<body>
<div id="forgeViewer"></div>
</body>
</html>
I could not reproduce the issue, however, one thing that will be causing problems is that you're not waiting for initialize_viewer() to finish before trying to load a document. Looks like when using the file:// protocol, you don't run into problems with that though.
I turned that function into a Promise so that we can await it.
Also, it's better to keep the client secret on the server side, so I added that to the code too. You'll just have to update the variables in server.py
This solution worked fine for me. Maybe your Flask app has some different settings that cause the issue?
I had index.html in the templates folder:
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=no" />
<meta charset="utf-8" />
<title>Axpo BIM Viewer</title>
<link rel="stylesheet" href="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.min.css" type="text/css" />
<script src="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<style>
body {
margin: 0;
}
#forgeViewer {
width: 100%;
height: 800px;
margin: 0;
background-color: #f0f8ff;
}
#forgeViewer>div {
height: 800px !important;
}
</style>
<script>
$(async() => {
let documentId = "urn:{{ documentId }}";
let htmlDiv = document.getElementById("forgeViewer");
let viewer = new Autodesk.Viewing.GuiViewer3D(htmlDiv);
await initialize_viewer(viewer);
load_document(viewer, documentId);
});
async function get_access_token(onTokenReady) {
var request = {
url: "/access_token",
method: "GET",
};
let res = await $.ajax(request);
console.log(res);
onTokenReady(res.access_token, res.expires_in);
}
async function initialize_viewer(viewer) {
return new Promise(resolve => {
var options = {
env: "AutodeskProduction2",
api: "streamingV2", // for models uploaded to EMEA change this option to 'streamingV2_EU'
getAccessToken: get_access_token,
};
Autodesk.Viewing.Initializer(options, () => {
var startedCode = viewer.start();
if (startedCode > 0) {
console.error("Failed to create a Viewer: WebGL not supported.");
return;
}
console.log("Initialization complete, loading a model next...");
resolve();
});
})
}
function load_document(viewer, documentId) {
console.log("Loading document");
Autodesk.Viewing.Document.load(
documentId,
(viewerDocument) => {
var defaultModel = viewerDocument.getRoot().getDefaultGeometry();
viewer.loadDocumentNode(viewerDocument, defaultModel);
},
() => {
console.error("Failed fetching Forge manifest");
}
);
}
</script>
</head>
<body>
<div id="forgeViewer"></div>
</body>
</html>
server.py
from flask import Flask, render_template
import requests
app = Flask(__name__)
# Update variable values
document_id=""
client_id=""
client_secret=""
#app.route("/")
def run():
return render_template('index.html', documentId=document_id)
#app.route("/access_token")
def get_access_token():
headers = {"Content-Type": "application/x-www-form-urlencoded"}
body = (
f"client_id={client_id}"
f"&client_secret={client_secret}"
"&grant_type=client_credentials"
"&scope=viewables:read"
)
print(body)
res = requests.post("https://developer.api.autodesk.com/authentication/v1/authenticate", data=body, headers=headers)
data = res.json()
return data
"Why are there CORS errors on localhost but not on file:///.../index.html?"
I guess the browser has different security concerns for the different protocols. The Viewer does not support the file:// protocol (only http:// / https://) and with some models you'll run into issues because of that - e.g. like this:

Django with Vue, detect form validation error

I have a Django ModelForm which is displayed in the template by using using crispy forms. After the user fills out the fields and presses a Submit button, an email is sent at the backend using Django's core send_email.
The problem is that the call to send_email is synchronous, so the user has to wait for the next page to load (success/failure page) but in this time the user might press the Submit button again and this generates multiple POSTs, making multiple emails.
I want to use Vue.js to make the button inactive once the user presses it but only if it passes Django's form validation. Is there a way to detect this?
Add to your button :disabled="!readyToSend" where readyToSend can be returned by your data function or a computed propoerty.
Before submitting the form set this variable to false, afater receiving data from your API, reset it to true.
In the following example I've choosen to make readyToSend a computed proporty where it will return true if the form is valid and if the process is not waiting for the API response.
The complete Code Pen example is here
html file :
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>example</title>
</head>
<body>
<div id="app">
<h2>{{ message }}</h2>
<form #submit.prevent>
<input type="text" v-model="dataToSend" placeholder="Something to send">
<button type="button" :disabled="!readyToSend" #click="send">Send</button>
</form>
</div>
</body>
</html>
javascript:
var vm = new Vue({
el: '#app',
data: function(){
return {
message: "please enter your message and click on send.",
dataToSend: "",
sentAndWaiting: false,
}
},
methods:{
send: async function(){
this.sentAndWaiting = true;
// Send Data Here
this.message = "sending....";
try{
let response = await fetch('https://jsonplaceholder.typicode.com/posts/1');
let jsonResponse = await response.json();
}
catch(e){
this.message = e.message;
}
// reponse received ... do Something with it
this.reponseReceived();
},
reponseReceived: function(){
this.sentAndWaiting = false;
this.message = "Ok. Got The response.";
}
},
computed:{
readyToSend: function(){
return this.dataToSend.length > 0 && !this.sentAndWaiting;
}
},
});
in my browser I had to test this by going to the developper tools and limit my internet connexion to the GPRS and disabling cache:
Screenshot DevTools

Testing asynchronously returned data

I have the following example:
<!DOCTYPE html>
<html>
<head>
<title>Mocha</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.1.2/mocha.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.5.0/chai.min.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-resource/1.0.3/vue-resource.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sinon.js/1.15.4/sinon.js"></script>
<script>mocha.setup('bdd');</script>
<script>
"use strict";
var assert = chai.assert;
var should = chai.should();
var vm = new Vue({
data: {
message: "Hello"
},
methods: {
loadMessage: function() {
this.$http.get("/get").then(
function(value) {
this.message = value.body.message;
});
},
}
});
describe('getMessage', function() {
let server;
beforeEach(function () {
server = sinon.fakeServer.create();
});
it("should get the message", function(done) {
server.respondWith([200, { 'Content-Type': 'application/json' },
JSON.stringify({message: "Test"})]);
vm.message.should.equal("Hello");
vm.loadMessage();
server.respond();
setTimeout(function() {
// This one works, but it's quirky and a possible error is not well represented in the HTML output.
vm.message.should.equal("Test");
done();
}, 100);
// This one doesn't work
//vm.message.should.equal("Test");
});
});
</script>
<script>
mocha.run();
</script>
</body>
</html>
I want to test that Vue asynchronously gets data from the server. Though, I mock out the actual HTTP request with Sinon FakeServer.
Naturally, directly after the call to loadMessage, the message is not yet set. I could use a timeout function for the test, but I believe there should be a better method. I've looked into respondImmediately, but it did not change. Also, there is the possibility to call a done() function. However, as I understand this, this done would need to be called within the loadMessage function, hence modifying the code under test.
What is the correct approach to handle this problem?
Edit: I have found at least a partial solution, but it seems to be still messy: call the done() function in the mocha unit test. When the assertion fails, it is at least shown in the HTML output. However, the assertion message is not as clear as in a normal test. Also, the technique still seems messy to me.
Since updating of vue component is done asynchronously you would need to use
// Inspect the generated HTML after a state update
it('updates the rendered message when vm.message updates', done => {
const vm = new Vue(MyComponent).$mount()
vm.message = 'foo'
// wait a "tick" after state change before asserting DOM updates
Vue.nextTick(() => {
expect(vm.$el.textContent).toBe('foo')
done()
})
})
Taken from official docs.

can't publish built-in actions via facebook app

Hi I have been dealing with facebook apps and actions a lot lately and it doesn't seem to work for me. Now I hear that the social button for likes is about to migrate and I can't figure out how to even publish the like built-in action via the app I created (it's an app for websites).
The code to publish actions I have taken from here: https://developers.facebook.com/docs/opengraph/tutorial/ and this is what they say:
<head prefix="og: http://ogp.me/ns# [YOUR_APP_NAMESPACE]:
http://ogp.me/ns/apps/[YOUR_APP_NAMESPACE]#">
<title>OG Tutorial App</title>
<meta property="fb:app_id" content="[YOUR_APP_ID]" />
<meta property="og:type" content="[YOUR_APP_NAMESPACE]:recipe" />
<meta property="og:title" content="Stuffed Cookies" />
<meta property="og:image" content="http://fbwerks.com:8000/zhen/cookie.jpg" />
other metatag properties etc.
<script type="text/javascript">
function postCook()
{
FB.api(
'/me/[YOUR_APP_NAMESPACE]:cook',
'post',
{ recipe: 'http://fbwerks.com:8000/zhen/cookie.html' },
function(response) {
if (!response || response.error) {
alert('Error occured');
} else {
alert('Cook was successful! Action ID: ' + response.id);
}
});
}
</script>
</head>
<body>
<div id="fb-root"></div>
<script>
window.fbAsyncInit = function() {
FB.init({
appId : '[YOUR_APP_ID]', // App ID
status : true, // check login status
cookie : true, // enable cookies to allow the server to access the session
xfbml : true // parse XFBML
});
};
// Load the SDK Asynchronously
(function(d){
var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;}
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
d.getElementsByTagName('head')[0].appendChild(js);
}(document));
</script>
etc etc.
<form>
<input type="button" value="Cook" onclick="postCook()" />
</form>
<fb:activity actions="[YOUR_APP_NAMESPACE]:cook"></fb:activity>
the fact is that I am not able to publish any action (returns an error) and of course my app has the actions I am trying to publish. Doesn anyone have a clue? I'm not a professional developer but the ones I asked had no solution...
Many thanks!
I found the problem!
Apparently the include function in PHP had the headed we used around the website conflict with the Facebook metadata. When I removed the headers and the "php start", all functions (comments included) were working.
It still seem a peculiar issue though: apparently Facebook's API doesn't clear the cache, so now we have to create new pages to enable functions. No way to have them in the current page locations.

Fatal error: Uncaught OAuthException: (#100)

I am getting a error
error_log: Could not set cookie. Headers already sent.
Fatal error: Uncaught OAuthException: (#100) link URL is not properly formatted thrown in /home/admin/facebook.php on line 453
The script worked a months ago so just uploaded it again and got a error. This is a custom built script so donno whats wrong so thought id ask here. Here is my code
require_once 'facebook.php';
require_once 'database.class.php';
require_once 'config.php';
/**
* FB Session
*/
$facebook = new Facebook(array(
'appId' => FB_APP_ID,
'secret' => FB_SECRET,
'cookie' => true,
));
$session = $facebook->getSession();
$params = array(
'canvas' => 1,
'fbconnect' => 0,
'next' => URL_CANVAS,
'req_perms' => 'publish_stream, offline_access'
);
$loginUrl = $facebook->getLoginUrl($params);
/**
* User authenticated?
*/
if ($session) {
try {
$fb_uid = $facebook->getUser();
$me = $facebook->api('/me');
$access_token = $session['access_token'];
$pg = 'main';
} catch (FacebookApiException $e) {
echo '<script type="text/javascript">top.location.href = "' . URL_CANVAS . '";</script>';
exit;
}
} else {
$pg = 'splash';
}
?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/2008/fbml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta http-Equiv="Cache-Control" Content="no-cache" />
<meta http-Equiv="Pragma" Content="no-cache" />
<meta http-Equiv="Expires" Content="0" />
<title>bloxorz worldst hardest game can you beat it ?</title>
<!--// using jQuery UI for this sample app //-->
<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js"></script>
<script type="text/javascript">
$(function() {
$("#tabs").tabs();
});
</script>
</head>
<body>
<!--// Facebook Javascript SDK needed for IFrame Canvas App //-->
<div id="fb-root"></div>
<script type="text/javascript" src="http://connect.facebook.net/en_US/all.js"></script>
<script type="text/javascript">
window.fbAsyncInit = function() {
FB.init({appId: '<?php echo FB_APP_ID; ?>', status: true, cookie: true, xfbml: true});
FB.Canvas.setSize();
};
(function() {
var e = document.createElement('script');
e.type = 'text/javascript';
e.src = document.location.protocol +
'http://connect.facebook.net/en_US/all.js';
e.async = true;
document.getElementById('fb-root').appendChild(e);
}());
</script>
<!--// START: page include //-->
<?php
if (!empty($pg)) {
include $pg . '.php';
} else {
echo '<b>Error:</b> Page Not Found.';
}
?>
<!--// END: page include //-->
or is the error in side the facebook.php page ?
Because the line 453 in facebook.php is
// results are returned, errors are thrown
if (is_array($result) && isset($result['error'])) {
$e = new FacebookApiException($result);
if ($e->getType() === 'OAuthException') {
$this->setSession(null);
}
throw $e;
}
return $result;
}
You have started "echoing" output before headers are being set. Facebook.php on line 453 is trying to set some response headers but it can't, because headers have already been set and body of response has started.
Make sure you don't echo, var_dump, pr or output anything to browser before line 453 is executed.