I am trying to get prerender working on both local and prod. I feel like I have tried all implementations. I am still getting no static html in the body when using: ?_escaped_fragment_= at the end of the URL.
Here is my current Meteor implementation:
Meteor.startup(() => {
var prerenderio = Npm.require('prerender-node');
var token;
var serviceUrl;
var protocol;
var settings = Meteor.settings.PrerenderIO;
token = process.env.PRERENDERIO_TOKEN || (settings && settings.token);
protocol = process.env.PRERENDERIO_PROTOCOL || (settings && settings.protocol);
// service url (support `prerenderServiceUrl` (for historical reasons) and `serviceUrl`)
serviceUrl = settings && (settings.prerenderServiceUrl || settings.serviceUrl);
serviceUrl = process.env.PRERENDERIO_SERVICE_URL || serviceUrl;
if (token) {
if (serviceUrl) prerenderio.set('prerenderServiceUrl', serviceUrl);
prerenderio.set('prerenderToken', token);
if (protocol) prerenderio.set('protocol', protocol);
prerenderio.set('afterRender', function afterRender(error) {
if (error) {
console.log('prerenderio error', error); // eslint-disable-line no-console
return;
}
});
WebApp.rawConnectHandlers.use(prerenderio);
}
});
I have my settings file set up as so:
"PrerenderIO": {
"serviceUrl": "http://localhost:3033/",
"token": "mytoken"
},
Same for prod but without the serviceUrl. I did get the prerender server up and the page renders....but its still the default Meteor script rendered page. I also tried: <script> window.prerenderReady = false; </script> and then set it to true after my API content has loaded via our router (using ButterCMS for site content.
I have of course also added: <meta name="fragment" content="!"> to our sites head.
Prerender is still saying its not seen our token get used. I think I could be missing something obvious here....but not certain what it is.
That seems like the prerender middleware is not being run. Does Meteor leave the rawConnectHandlers in the order that they are added? Can you try this:
WebApp.rawConnectHandlers.use(function(req, res, next) {
console.log('before prerender:', req.url)
});
WebApp.rawConnectHandlers.use(prerenderio);
And see if you see any output in your logs for that showing what the incoming URL looks like. If you are accessing the ?_escaped_fragment_= URL, you should see get printed in that console.log statement.
Feel free to email us at support#prerender.io with a URL if you'd like us to help test.
Related
As the title says, how would i implement that the content script would be injected according to user specified websites?
Right now i'm saving user specified websites in a Storage.
I tried doing it programmatically by injecting content script upon tab creation/update but i had issues with cross origin stuff, and couldn't get it to work.
const injectContentScripts = function(sites)
{
// if not provided, use default.
if (!sites)
sites = 'website.com'
const urls = sites.split('\n').map(url => `*://${url}/*`)
chrome.tabs.query({ url:urls, status:'complete' }, function(tabs) {
for (const tab of tabs)
{
chrome.tabs.executeScript(tab.id, { file:'ext/jquery.min.js', allFrames:true, runAt:'document_end' }, function(){
chrome.tabs.executeScript(tab.id, { file:'content.js', allFrames:true, runAt:'document_end' })
})
}
})
}
I also had an idea i could just format manifest.json file upon user updating what websites user would like to add but that wouldn't work as when you pack an extension it becomes that .crx file or somethiing, right?
I tried searching about this case but couldn't find anyone asking this. Sorry if i'm missing something, i'm not that great in English.
Actually found solution myself. :D
Well, if anyone needs something like this as well. I got it working with this,
chrome.webNavigation.onDOMContentLoaded.addListener(async (e) => {
const saved = await chrome.storage.sync.get(['sites'])
// if not provided, use default.
if (!saved.sites)
saved.sites = 'website.com'
const split = e.url.split('://')
const splitUrl = split[1] ? split[1].split('/')[0] : false
// if not url or frame is not main frame return.
if (!splitUrl || e.frameId != 0)
return
for (const url of saved.sites.split('\n'))
{
if (splitUrl.match(url))
{
injectContentScripts(e.tabId)
break;
}
}
})
const injectContentScripts = async function(tabId)
{
if (tabId == undefined)
return
try {
// i'm using this npm library, whichh allows async await https://www.npmjs.com/package/chrome-extension-async
await chrome.tabs.executeScript(tabId, { file:'ext/jquery.min.js', allFrames:true, runAt:'document_end' })
await chrome.tabs.executeScript(tabId, { file:'content.js', allFrames:true, runAt:'document_end' })
} catch(e) {
console.error(e)
}
}
There is integrated url matcher, but i wasn't sure how i could access those functions. Anyway this works fine, cleaner way would be with RegExp but it works so it's fine i guess. :D
In order this to work, you have to specify these permissions in manifest file.
"permissions": [
"webNavigation",
"<all_urls>"
],
I am on my way to separate the code for my API in different modules using the exports object, as it is the most similar way to the ES6 standard (not yet supported by Node).
Here is my code currently (it can be run as shown), the problem is that, after separating, the function "cleanFormData" gets called fine, but stops without returning anything (observe the comments starting with "STACK OVERFLOW"):
File: main.js
// Dependencies:
const express = require('express');
const bodyParser = require('body-parser');
// Define app:
const app = express();
// API v0 code:
const apiV0 = require('./api0_sources/api');
// Configuration variables:
const consoleLogActions = true;
// Server start:
app.listen(8888, function () {
console.log('Server running on port ' + this.address().port + ' - ' + new Date());
});
// For parsing every application/json request:
app.use(bodyParser.json());
// Submit data to register an user:
app.post('/registration', function (req, res) {
res.set({'Content-Type': 'application/json'});
// Clean the required data after obtaining it from the parsed JSON:
let cleanedFormData = apiV0.cleanFormData({ // STACK OVERFLOW: The code stops working here.
username: req.body.formdata.username,
email: req.body.formdata.email,
phone: req.body.formdata.phone,
password: req.body.formdata.password
});
// The "required or not" policy is enforced here (after the strings have been cleaned to prevent blank fields to pass):
errors = [];
if (cleanedFormData.username === undefined) errors.push('username_required');
if (cleanedFormData.email === undefined) errors.push('email_required');
if (cleanedFormData.phone === undefined) errors.push('phone_required');
if (cleanedFormData.password === undefined) errors.push('password_required');
if (errors.length > 0) {
let result = {
success: false,
errors: errors
};
res.jsonp(result);
}
})
// [More things happen after]
File: ./api0_sources/api.js
// Fix and delete object's strings (for data coming from user's inputs):
exports.cleanFormData = function(object) {
for (let i in object) {
object[i] = String(object[i]); // Convert all the object properties to strings (to prevent problems with true, false and null).
if ((object[i] === 'undefined') || (!object[i].replace(/\s/g, '').length)) { // Deletes 'undefined' strings, empty strings and the ones containing only spaces.
delete object[i];
continue; // Skip to the next loop after the property is removed.
}
// Do not try to fix the "password" or "newPassword" property:
if ((i === 'password') || (i === 'newPassword')) continue;
// Replace multiple spaces with a single one:
object[i] = object[i].replace(/\s\s+/g, ' ');
// Check if it is "trimmable" and if so, trim the string:
if (object[i].trim()) object[i] = object[i].trim();
console.log(object[i]) // Observing iterations.
}
if (consoleLogActions) console.log('▼ Cleaned object keys:\n', object);
return object;
};
Before, everything was in the same file and worked perfectly fine! Can someone help me to identify what has triggered this unexpected behaviour?
UPDATED 1: Apparently, I identified the problem: I had a variable not shown in the example before: "consoleLogActions", that was only defined in the main file and apparently this stopped the function in the child module from finishing. However, absolutely no error was being thrown by Node. In the updated example it does, in my actual file it doesn't (still, no idea why).
UPDATE 2: Thanks, Marcos Casagrande. It seems like this Express middleware is catching the wrong exceptions. I had literally no idea that this could affect the rest of the code nor I have idea how to fix it. Any suggestions?:
// Detecting syntax errors (depending on the "application/type"):
app.use(function(err, req, res, next) {
if (err instanceof SyntaxError) { // If "SyntaxError" is part of the error object:
res
.status(400)
.jsonp({
success: false,
errors: ['bad_syntax']
});
}
});
Apparently, I identified the problem: I had a variable not shown in
the example before: "consoleLogActions", that was only defined in the
main file and apparently this stopped the function in the child module
from finishing. However, absolutely no error was being thrown by Node.
In the updated example it does, in my actual file it doesn't (still,
no idea why).
If you're not getting any error, you probably have an express error-handling middleware, that it's not logging the error.
app.use((err, req, res, next) => {
// console.error(err); // I'm not doing this.
res.status(500).end();
});
Or you have an uncaughtException listener somewhere in your code.
process.on('uncaughtException', () => {});
The code above, will prevent uncaught errors from being logged, and the process from crashing. This is a really bad practice, and you should avoid it.
Check the following question:
Node.js Best Practice Exception Handling
I'm upgrading/rewriting an existing angular app to use angular2. My problem is that I want to open a OAuth flow in a new pop up window and once the OAuth flow is completed use window.postMessage to communicate back to the angular 2 app that the OAuth flow was successful.
Currently what I have is in the angular 2 service is
export class ApiService {
constructor(private _loggedInService: LoggedInService) {
window.addEventListener('message', this.onPostMessage, false);
}
startOAuthFlow() {
var options = 'left=100,top=10,width=400,height=500';
window.open('http://site/connect-auth', , options);
}
onPostMessage(event) {
if(event.data.status === "200") {
// Use an EventEmitter to notify the other components that user logged in
this._loggedInService.Stream.emit(null);
}
}
}
This template that is loaded at the end of the OAuth flow
<html>
<head>
<title>OAuth callback</title>
<script>
var POST_ORIGIN_URI = 'localhost:8000';
var message = {"status": "200", "jwt":"2"};
window.opener.postMessage(message, POST_ORIGIN_URI);
window.close();
</script>
</head>
</html>
Using window.addEventListener like this seems to completely break the angular 2 app, dereferencing this.
So my question is can I use window.addEventListener or should I not use postMessage to communicate back to the angular2 app?
** Complete angular2 noob so any help is appreciated
I have a complete Angular2 OAuth2 skeleton application on Github that you can refer to.
It makes use of an Auth service for OAuth2 Implicit grants that in turn uses a Window service to create the popup window. It then monitors that window for the access token on the URL.
You can access the demo OAuth2 Angular code (with Webpack) here.
Here is the login routine from the Auth service, which will give you an idea of what's going on without having to look at the entire project. I've added a few extra comments in there for you.
public doLogin() {
var loopCount = this.loopCount;
this.windowHandle = this.windows.createWindow(this.oAuthTokenUrl, 'OAuth2 Login');
this.intervalId = setInterval(() => {
if (loopCount-- < 0) { // if we get below 0, it's a timeout and we close the window
clearInterval(this.intervalId);
this.emitAuthStatus(false);
this.windowHandle.close();
} else { // otherwise we check the URL of the window
var href:string;
try {
href = this.windowHandle.location.href;
} catch (e) {
//console.log('Error:', e);
}
if (href != null) { // if the URL is not null
var re = /access_token=(.*)/;
var found = href.match(re);
if (found) { // and if the URL has an access token then process the URL for access token and expiration time
console.log("Callback URL:", href);
clearInterval(this.intervalId);
var parsed = this.parse(href.substr(this.oAuthCallbackUrl.length + 1));
var expiresSeconds = Number(parsed.expires_in) || 1800;
this.token = parsed.access_token;
if (this.token) {
this.authenticated = true;
}
this.startExpiresTimer(expiresSeconds);
this.expires = new Date();
this.expires = this.expires.setSeconds(this.expires.getSeconds() + expiresSeconds);
this.windowHandle.close();
this.emitAuthStatus(true);
this.fetchUserInfo();
}
}
}
}, this.intervalLength);
}
Feel free to ask if you have any questions or problems getting the app up and running.
So with a bit of investigation found out the problem. I was de-referencing this. This github wiki helped me understand it a bit more.
To solve it for my case needed to do a couple of things. Firstly I created a service that encapsulated the adding of an eventListener
import {BrowserDomAdapter} from 'angular2/platform/browser';
export class PostMessageService {
dom = new BrowserDomAdapter();
addPostMessageListener(fn: EventListener): void {
this.dom.getGlobalEventTarget('window').addEventListener('message', fn,false)
}
}
Then using this addPostMessageListener I can attach a function in my other service to fire
constructor(public _postMessageService: PostMessageService,
public _router: Router) {
// Set up a Post Message Listener
this._postMessageService.addPostMessageListener((event) =>
this.onPostMessage(event)); // This is the important as it means I keep the reference to this
}
Then it works how I expected keeping the reference to this
I think this is the Angular2 way:
(Dart code but TS should be quite similar)
#Injectable()
class SomeService {
DomAdapter dom;
SomeService(this.dom) {
dom.getGlobalEventTarget('window').addEventListener("message", fn, false);
}
}
I fiddled around with this for ages but in the end, the most robust way for me was to redirect the user to the oath page
window.location.href = '/auth/logintwitter';
do the oath dance in the backend (I used express) and then redirect back to a receiving front end page...
res.redirect(`/#/account/twitterReturn?userName=${userName}&token=${token}`);
There are some idiosyncracies to my solution because e.g. I wanted to use only JsonWebToken on the client regardless of login type, but if you are interested, whole solution is here.
https://github.com/JavascriptMick/learntree.org
I'm trying to create a todo-list app and checking it with POSTMAN on every type of request. GET and POST requests are working fine. But PUT and DELETE are not working as expected.
As expected means : It is not showing 404 neither showing results instead it's showing an error, i.e., Could not get any response.
Here's my code of put and delete :
var todos = [];
// DELETE /todos/:id
app.delete('/todos/:id', function(req,res){
var todoId = parseInt(req.params.id, 10);
var matchedTodo = _.findWhere(todos, {id: todoId});
if(!matchedTodo){
res.status(404).send();
}else{
todos = _.without(todos, matchedTodo);
res.json(matchedTodo);
//console.log(todos);
}
});
// PUT /todos/:id
app.put('/todos/:id', function(req,res){
var todoId = parseInt(req.params.id, 10);
var matchedTodo = _.findWhere(todos, {id: todoId});
var body = _.pick(req.body, 'description', 'completed');
var validAttributes = {};
if(!matchedTodo){
return res.status(404).send();
}
if(body.hasOwnProperty('completed') && _.isBoolean(body.completed)){
validAttributes.completed = body.completed;
}else if(body.hasOwnProperty('completed')){
return res.status(400).send();
}
if(body.hasOwnProperty('description') && _.isString(body.description) && body.description.trim().length > 0){
body.description = body.description.trim();
validAttributes.description = body.description;
}else if(body.hasOwnProperty('description')){
return res.status(400).send()
}
_.extend(matchedTodo, validAttributes);
res.json(matchedTodo);
});
Now, I don't know if it's a bug or something.
First, you should try the official tips:
The server couldn't send a response: Ensure that the backend is working properly
Self-signed SSL certificates are being blocked: Fix this by turning off 'SSL certificate verification' in Settings > General
Proxy configured incorrectly: Ensure that proxy is configured correctly in Settings > Proxy
Request timeout: Change request timeout in Settings > General
Second, normally official tip 2 will solve this problem. If not, you may use environment variables. You should confirm the link (shown in the picture, the protocol) can be accessed.
If not, you should check your environment variable's current value. If the current value is blank, you should fill in and set the options you've changed in official tips to default. And the problem should be solved now.
Using NodeJs I'm trying to do something quite similar to Meteor: I want to send only the parts of a page that actually changed. My dilemma is that I know how to create such a framework to respond to link clicks and send partial updates but such a framework won't cater to direct browsing to a page other than the index (which is what is required for search engines and people without javascript to use your site).
I can also figure out how to make a framework to support entire page reloads, handlebars and a simple node server instance would take care of that. Hoeever, I can't figure out how to create a way that would allow me to write one method to tell the framework the partial updates for a page and let the framework figure out what else needs to be loaded.
A way I can think of would be to create the index page every time (for entire page loads) and apply partial updates to that but that can quickly become expensive if a subpage differs a lot from a very crowded index.
An example method would look something like this:
function images(id) {
if (id == null) {
// load all images from database
template.images = db.result();
template.content = template.loadblock('gallery');
}
else {
// retrieve single image
template.content = template.loadblock('single_image');
template.image = db.result();
}
}
On a partisl updste calling this method for domain.com/images would work just fine because it's clear what had changed.
For an entire page load this function would miss things like a header, footer ,navigation, etc.
In an answer I would look for an example where this has been done or some tips that Can point me in the right direction. I'm sorry for any typoes I wrote this post on an ipad. If you have any questions about my question just ask and I'll update as needed.
Update:
A possible example of a solution might be the following code. It's to give an idea, it probably won't actually run
// As a convention, don't pass around raw values if they aren't static but pass around functions such as
data.images = function () {
// run db query
// return an object with the images
}
// This constraint might be limited to the index() method
var routes = {
// This now allows us to do things like this:
index: function() {
var data;
// Initialise everything needed for the index
data.title = 'Index';
data.nav = { Index: '/', Images: '/images' };
data.content = 'Hello World';
},
categories: function() {
var data;
data.content = render('gallery', function () { /* load and return images as object */ }); // Not sure about this dynamic subtemplating but oh well
}
// This now allows us to do the following:
function request(page, type) {
if (type == 'update') {
if (routes[page] != undefined && typeof routes[page] == 'function') {
respond(routes[page]());
}
}
else {
if (routes[page] != undefined && typeof routes[page] == 'function') {
var data = mergeArrays(routes['index'](), routes[page]());
// index.html which is just a Handlebars template
respond(data);
}
}
}
Here is a pattern I often use (in Express apps):
function respond(req, res, name, resource) {
if(req.accepts('json')) {
// Send JSON to clients who want it
res.send(resource);
} else {
// Render with layout only for non-XHR requests
resource.layout = !req.xhr;
res.render('resources/' + name, resource);
}
}
Example usage:
app.get('/images', function(req, res, next) {
getImages(function(err, images) {
if(err) return next(err);
respond(req, res, 'images', images);
});
});
app.get('/images/:id', function(req, res, next) {
getImage(req.params.id, function(err, image) {
if(err) return next(err);
respond(req, res, 'image', image);
});
});
image.jade:
img(src=uri, alt=title)
images.jade:
#gallery
for image in images
include image
Clients who ask for JSON get that, otherwise they get the full page only if it's a non-XHR request. XHR requests get just the HTML snippet for the requested resource. This works well for quite simple apps, where resources mostly correspond to pages.