Send file - remove parameters - javascript

So I'm navigating to a site like /subsite with a specific URL that hold the key parameter:
https://my.server.com/premiumSite?key=secretKey
or
https://my.server.com/premiumSite.html?key=secretKey
I'd like to determine if the key parameter is "secretKey". If it is I'd like to send a file (not redirect!) called premiumSite.html to the user. If the key is something different I'd like to redirect the user to the index.html site to don't give the user access to my site.
The whole routine is working absolutely fine - but (of course) theres one problem:
The function res.sendFile() (like I want it to be) does not change the URL at all - what also effects the parameters.
So how can I call sendFile() but also remove the "key" parameter from the URL at the same time (not using local js)?
let app = express();
app.use('/premiumSite(.html)?',function(req, res) {
let isPremiumUser = req.query.key === "secretKey"; // check if URL parameters are matching
if (isPremiumUser) res.sendFile("www/premiumSite.html", {root: __dirname});
else res.redirect("www/index.html");
})

Related

Send message from WeChat mini-program to web-view

I'm building WeChat Mini-Program that on one of it's pages has web-view control. For example:
page.wxml
<web-view src="https://..." bindmessage="onWebViewMessage"></web-view>
page.js
const app = getApp();
Page({
onWebViewMessage:function(e) {
console.log(e);
},
onLoad:function() {
}
});
In web-view an HTML page is loaded (index.html), that includes jweixin-1.3.2.js lib from WeChat, for connecting with WeChat API as well as connect to parent Mini-program. Page is empty, no DOM elements, just javascript that will execute when document is loaded.
It has it's javascript something like this:
index.js
document.addEventListener('DOMContentLoaded',function(){
wx.miniProgram.postMessage({data:'test'});
});
I am able to post messages from this document to mini-program without issues. Also can send some mini-program navigation commands such as wx.miniProgram.navigateTo({url:'path/to/page'}); so all seems fine. I can also get callback in Mini-program when web-view has completed loading.
Question:
How can I post message from Mini-program to web-view? For example, to pass a string or an Object to the web-view.
I have been googling for hours and can't seem to find anyone doing it, but I can't believe it's just one-way communication possible.
Any help or idea is appreciated!
I have found an effective way to pass data from mini-program to web-view content, and it seems at this moment in time, this is the only possible way to do it.
Mini-program
1. Base64 module
You will need to be able to convert normal String into Base64 string. Mini-program API has a method for converting byte array into base64 string, but that won't be usable for this purpose. So, create your own module that does that:
File: lib/b64.js
var string2base64 = function(str) {
.... here put your js code for making b64 string ....
return result;
};
module.exports = {
string2base64
};
2. Page with Web-View
In the page that has web-view control, prepare DOM element in wxml file like this:
File: pages/xxx/index.wxml
<web-view src="{{webURL}}" bindload="onWebLoad" binderror="onWebError"></web-view>
Notice that src parameter is now bound to page's webURL property. Whenever page sets value to this property, will automatically be applied to the DOM elemenet.
In file pages/xxx/index.js you will need to add base64 module:
const b64 = require('../../lib/b64.js')
note that require path may vary depending how you have setup your project
and in page's data object, add webURL and webBaseURL properties, like this:
Page({
data: {
webURL:'',
webBaseURL:'https://your/web/app/url',
messageQueue:[],
messageQueueSize:0,
.... other page properties go here ....
},
..... rest of your page code goes here .....
})
Notice that webURL is set to be empty. This means that when page loads, an empty string will be set to DOM object by default.
webBaseURL will explain just in a bit.
messageQueue is an Array that will store pending messages to be sent to web-view.
messageQueueSize is just Array length. Used for better performance, to avoid reading Array.length.
3. Start Message Queue
In onShow callback of the page, set webURL and start interval that will read messageQueue Array every 250ms. You can change the way this is done if you dislike using intervals, this was just simplest way to do theory test.
onShow: function(){
// This will start loading of the content in web-view
this.setData({webURL: this.data.webBaseURL } );
// Sends message from message queue to web-view
let _this = this;
setInterval(function(e) {
if( _this.data.messageQueueSize < 1 ) return;
_this.data.messageQueueSize --;
let msg = _this.data.messageQueue.splice(0,1);
_this.setData({webURL: _this.data.webBaseURL+"#"+msg});
},250);
}
You can see that message is appended to web-view source (url) as a hash.
webBaseURL is used to generate final URL with hash, that is then send to web-view.
4. Add a Message to the Queue
To create a message in message queue, just define following method in your page:
addMessageToQueue: function(obj) {
obj.unique = Math.round(Math.random()*100000);
let msg = b64.string2base64(JSON.stringify(obj));
this.data.messageQueue.push(msg);
this.data.messageQueueSize++;
}
Whenever you call this method, just pass an Object with whatever properties you need it to have, and it will be converted into JSON string, then to base64 string, and finally appended to the message queue.
unique property is added to make generated base64 result always different even if the rest of object properties are the same - I just needed this for the purpose of my project. You can ignore it / remove it if you do not need it.
Since there's interval running and checking on the message queue, all messages added like this will be sent to web-view in the same order they were added to the queue.
Now there's only one thing left - to add hash change listening in the HTML page we have loaded into the web-view:
HTML Web-app
1. Listen to hash change
window.addEventListener("hashchange",function(e){
let messageBase64 = window.location.hash.substr(1);
let json = window.atob( messageBase64 );
let data = JSON.parse(json);
console.log("Received data from mini-program:",data);
});
Tested on Xiaomi Mi8 Pro. I am yet to test on other devices sold in China.
Cheers!

How can I use "?param=X" in a route in NodeJS?

In my nodejs and "express" application I have this:
app.get("/:page?", function (req, res) {
var pg = req.params.page;
which corresponds to localhost:123 or localhost:123/3.
However, I want to be able to get the current page the same way by req.params.page, but the url should be localhost:123 or localhost:123/?page=X.
How?
My first suggestion is that you do not define your endpoint with /:variable. This will then match any route you create that follows that pattern. You should instead use something like /pages/:page to get a specific page
Then, you should use the URL parameter functionality in Express to include a URL parameter.
Define your route like this:
app.get("/pages/:page", function (req, res) {
var pg = undefined;
if (req.query.page) {
pg = req.query.page;
}
}
You can then access the page in req.query.page if it exists and do whatever you want with that value.
So for example, you could submit a request with localhost/pages/123?page=3.
req.query.page would equal 3 and req.params.page would equal 123.
You should instead define your route without the ? and if you do a get operation, for example on localhost:123/3?this=that then the req.query.this will be populated with the value that

Detecting Unique Browser Tabs

On every page of my sites, I am using AJAX to poll the server and retrieve a list of messages. The server maintains a list of messages and the SessionId (I'm in an ASP.NET environment, but I feel like this question is applicable to any server side technology) that the message is intended for. If a message is found for the particular SessionId, it is returned to the client side script. I use a JavaScript library to create a notification (using noty, a Jquery Notification Plugin). Once it returns a particular message, the server discards that message.
This works well if the user only has a single tab/window open for a particular site. However, let's say they have two open and they do something that causes a warning message to be generated. I have no control over which tab the notification goes to, so the user may not end up seeing the warning message.
Is there a way of uniquely identifying a browser tab? Then I could pass this as one of the parameters in my AJAX call.
Firstly, polling doesn't seem good mechanism. It might hit your server down when you have large number of active users. Ideally you should return a message in the response to the request that was result of invalid action.
Still below solution might work for you. It is inspired by the reply of #SergioGarcia.
Keep a hidden input just before the end of your form tag, which stores a unique ID for identifying a tab uniquely. You will store the messages on server session against unique tabID,
<input type="hidden" id="hiddenInputTabId" value="<%=getValue()%>" />
and then define getValue.
function string getValue() {
var v = getValueFormBodyOrAccessValueDirectlyByMakingInput_a_ServerSideControl();
if (string.IsNullOrWhiteSpace(v)) {
return Guid.NewId();
} else {
return v;
}
}
Because it is a hidden input you should get it's value in the POSTed form body, and for ajax requests below snippet should take care of sending that value in header which you can access on server side.
$.ajaxSetup({
beforeSend: function(xhr) {
xhr.setRequestHeader("tabId", $('#hiddenInputTabId').val());
},
});
Same header can be check while returning the response to your polling requests and only respond message available against the provided tabId should be responded.
You can add a query string parameter called tabId and control it's binding to tab using javascript.
There is a functional prototype below:
$(function () {
// from: https://developer.mozilla.org/en-US/docs/Web/API/Window.location
function getQueryStringParameter (sVar) {
return decodeURI(window.location.search.replace(new RegExp("^(?:.*[&\\?]" + encodeURI(sVar).replace(/[\.\+\*]/g, "\\$&") + "(?:\\=([^&]*))?)?.*$", "i"), "$1"));
}
// from: http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
function newGuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
}
window.tabId = getQueryStringParameter('tabId');
// tabId not defined, reload the page setting it
if (!window.tabId) {
window.tabId = newGuid();
}
// on click set the tabId of each link to current page
$(document).on('click', 'a', function (e) {
var $this = $(this);
var newLocation = $(this).attr("href");
// In page links
if (newLocation.match(/^#.+$/)) {
return;
}
// Outbound links
if (newLocation.match(new RegExp("^https?")) && !newLocation.match(new RegExp("^https?://" + window.location.host))) {
return;
}
// Insert tab id
if (newLocation.match(/(\?|&)tabId=[0-9a-f-]+/)) {
newLocation.replace(/(\?|&)tabId=[0-9a-f-]+/, (newLocation.indexOf('?') == -1 ? "?" : "&") + "tabId=" + window.tabId);
} else {
newLocation += (newLocation.indexOf('?') == -1 ? "?" : "&") + "tabId=" + window.tabId;
}
window.location.href = newLocation;
e.preventDefault();
});
});
If you enter a page in your application without setting the tabId parameter on query string, it will be set to a new UUID (Guid).
When the page has a tabId parameter on query string, it defines the window.tabId variable inside your page and you can use that in your application.
When the user click on any link in your page, a javascript event will be triggered and the link url will be redirected to match the current tabId. An right click to open in new tab or a middle click will not trigger that event and the user will be sent to a new tab without the query string parameters, so the new page will create a new tabId in that page.
You can see it working here: http://codepen.io/anon/pen/sCcvK
You can do it by generating a unique tab id with javascript by loading your client.
I strongly recommend you to use something for intertab communication, like intercom.js, which can broadcast the messages from a single tab with a single connection to every other tabs. Intertab works with socket.io, which has long polling fallback, so it may be good in your current system as well. I agree that polling is a poor choice, and you should use websockets instead.
If you use ZMQ on the server, then in the browser you can use NullMQ either (for websockets ofc). I think it does not have intertab support, so you should make your own intertab solution to make it work. It is not so hard to write such a system, you need only a common storage, for example localStorage, but it can be even cookie... If you don't have a storage event, you have to ping that storage for changes with setInterval. You have to store there the messages, and which tab broadcasts them (probably in a semaphore) and when was the last time it pinged the storage. After that you can keep each tab in sync with the others, or by using a unique tab id, you can send customized messages to any of the tabs. If the broadcast tab has a storage timeout (it did not ping the storage for a long while), then it is probably closed, so you should assign the broadcast service to another tab.
So what I ended up doing was changing how my notification framework functioned in order to prevent the need for identifying unique tabs. It's just too hard to layer information on the stateless web.
Instead of using Ajax to pump messages out to the client instantly, I build them up on each page into a List<Message> property. On PreRender I render them to the client with ClientScript.RegisterStartupScript(). But if I need to send the user to another page, I started using Server.Transfer() instead of Response.Redirect() instead so that it will preserve the message queue. The new page checks the old page to see if it exists and if is the correct Type. If it is the correct type, I cast it and retrieve the message queue from the old page and add them to the new page's queue. And since Server.Transfer() doesn't update the URL on the client, I also added a JavaScript function to manually push the state to the URL in supported browsers.
I know I took this in a little different direction than I did on the question, but I think I had been approaching it wrong in the beginning.

Express - Send a page AND custom data to the browser in a single request?

How simultaneously to render a page and transmit my custom data to browser. As i understood it needs to send two layers: first with template and second with JSON data. I want to handle this data by backbone.
As i understood from tutorials express and bb app interact as follows:
res.render send a page to browser
when document.ready trigger jQuery.get to app.get('/post')
app.get('/post', post.allPosts) send data to page
This is three steps and how to do it by one?
var visitCard = {
name: 'John Smit',
phone: '+78503569987'
};
exports.index = function(req, res, next){
res.render('index');
res.send({data: visitCard});
};
And how i should catch this variable on the page- document.card?
I created my own little middleware function that adds a helper method called renderWithData to the res object.
app.use(function (req, res, next) {
res.renderWithData = function (view, model, data) {
res.render(view, model, function (err, viewString) {
data.view = viewString;
res.json(data);
});
};
next();
});
It takes in the view name, the model for the view, and the custom data you want to send to the browser. It calls res.render but passes in a callback function. This instructs express to pass the compiled view markup to the callback as a string instead of immediately piping it into the response. Once I have the view string I add it onto the data object as data.view. Then I use res.json to send the data object to the browser complete with the compiled view :)
Edit:
One caveat with the above is that the request needs to be made with javascript so it can't be a full page request. You need an initial request to pull down the main page which contains the javascript that will make the ajax request.
This is great for situations where you're trying to change the browser URL and title when the user navigates to a new page via AJAX. You can send the new page's partial view back to the browser along with some data for the page title. Then your client-side script can put the partial view where it belongs on the page, update the page title bar, and update the URL if needed as well.
If you are wanting to send a fully complete HTML document to the browser along with some initial JavaScript data then you need to compile that JavaScript code into the view itself. It's definitely possible to do that but I've never found a way that doesn't involve some string magic.
For example:
// controller.js
var someData = { message: 'hi' };
res.render('someView', { data: JSON.stringify(someData) });
// someView.jade
script.
var someData = !{data};
Note: !{data} is used instead of #{data} because jade escapes HTML by default which would turn all the quotation marks into " placeholders.
It looks REALLY strange at first but it works. Basically you're taking a JS object on the server, turning it into a string, rendering that string into the compiled view and then sending it to the browser. When the document finally reaches the browser it should look like this:
// someSite.com/someView
<script type="text/javascript">
var someData = { "message": "hi" };
</script>
Hopefully that makes sense. If I was to re-create my original helper method to ease the pain of this second scenario then it would look something like this:
app.use(function (req, res, next) {
res.renderWithData = function (view, model, data) {
model.data = JSON.stringify(data);
res.render(view, model);
};
next();
});
All this one does is take your custom data object, stringifies it for you, adds it to the model for the view, then renders the view as normal. Now you can call res.renderWithData('someView', {}, { message: 'hi' });; you just have to make sure somewhere in your view you grab that data string and render it into a variable assignment statement.
html
head
title Some Page
script.
var data = !{data};
Not gonna lie, this whole thing feels kind of gross but if it saves you an extra trip to the server and that's what you're after then that's how you'll need to do it. Maybe someone can think of something a little more clever but I just don't see how else you'll get data to already be present in a full HTML document that is being rendered for the first time.
Edit2:
Here is a working example: https://c9.io/chevex/test
You need to have a (free) Cloud9 account in order to run the project. Sign in, open app.js, and click the green run button at the top.
My approach is to send a cookie with the information, and then use it from the client.
server.js
const visitCard = {
name: 'John Smit',
phone: '+78503569987'
};
router.get('/route', (req, res) => {
res.cookie('data', JSON.stringify(pollsObj));
res.render('index');
});
client.js
const getCookie = (name) => {
const value = "; " + document.cookie;
const parts = value.split("; " + name + "=");
if (parts.length === 2) return parts.pop().split(";").shift();
};
const deleteCookie = (name) => {
document.cookie = name + '=; max-age=0;';
};
const parseObjectFromCookie = (cookie) => {
const decodedCookie = decodeURIComponent(cookie);
return JSON.parse(decodedCookie);
};
window.onload = () => {
let dataCookie = getCookie('data');
deleteCookie('data');
if (dataCookie) {
const data = parseObjectFromCookie(dataCookie);
// work with data. `data` is equal to `visitCard` from the server
} else {
// handle data not found
}
Walkthrough
From the server, you send the cookie before rendering the page, so the cookie is available when the page is loaded.
Then, from the client, you get the cookie with the solution I found here and delete it. The content of the cookie is stored in our constant. If the cookie exists, you parse it as an object and use it. Note that inside the parseObjectFromCookie you first have to decode the content, and then parse the JSON to an object.
Notes:
If you're getting the data asynchronously, be careful to send the cookie before rendering. Otherwise, you will get an error because the res.render() ends the response. If the data fetching takes too long, you may use another solution that doesn't hold the rendering that long. An alternative could be to open a socket from the client and send the information that you were holding in the server. See here for that approach.
Probably data is not the best name for a cookie, as you could overwrite something. Use something more meaningful to your purpose.
I didn't find this solution anywhere else. I don't know if using cookies is not recommended for some reason I'm not aware of. I just thought it could work and checked it did, but I haven't used this in production.
Use res.send instead of res.render. It accepts raw data in any form: a string, an array, a plain old object, etc. If it's an object or array of objects, it will serialize it to JSON for you.
var visitCard = {
name: 'John Smit',
phone: '+78503569987'
};
exports.index = function(req, res, next){
res.send(visitCard};
};
Check out Steamer, a tiny module made for this this exact purpose.
https://github.com/rotundasoftware/steamer
Most elegant and simple way of doing this is by using rendering engine (at least for that page of concern). For example use ejs engine
node install ejs -s
On server.js:
let ejs = require('ejs');
app.set('view engine', 'ejs');
then rename desired index.html page into index.ejs and move it to the /views directory. After that you may make API endpoit for that page (by using mysql module):
app.get('/index/:id', function(req, res) {
db.query("SELECT * FROM products WHERE id = ?", [req.params.id], (error, results) => {
if (error) throw error;
res.render('index', { title: results[0] });
});
});
On the front-end you will need to make a GET request, for example with Axios or directly by clicking a link in template index.ejs page that is sending request:
<a v-bind:href="'/index/' + co.id">Click</a>
where co.id is Vue data parameter value 'co' that you want to send along with request

Routing system: pass email in parameters

I need my app execute a function when the URL looks like this
domain.com/mobile/#email_confirmed/email#address.com/otherparam
I added this route to one of my controller:
'email_confirmed/:email/:first': 'emailConfirmed'
as well as this function:
emailConfirmed: function (email, first)
But the function never gets called... However if I change go to this url :
domain.com/mobile/#email_confirmed/emailaddresscom/otherparam
then it works fine. I guess the problem comes from the at symbol and the dots in the email address. Therefore, I was wondering if there is another way of declaring the route so that it accepts email address.
Nice question,
There are a couple hoops you have to jump through to do this, firstly you need to encode or parse in your '#' symbol. Do this either by encoding to %40 OR by passing additional parameters. For example /myemail%40gmail/com/first OR /myemail/gmail/com/first, then create function(usernamedomain, tlDomain, first) OR function(username, domain, tlDomain, first) respectively. Then inside function decode the %40 OR parse together the address.
The other way I could see you solving this would include bypassing the routing system altogether. Instead of creating a link for your user to interact with create a Sencha Component that will fire an event you can listen for (list, button etc..), then inside your controller you can either use the data inside a function in that controller or call another controller function using this.getApplication().getController('SomeOtherController').handleEmail(email, first);
I have not tried it but with the last option you should not have to encode your url at all.
Again, nice question. Let me know if there are some other specifics,
Good luck, Brad
The solution I came up with is to set up the route like this:
'email_confirmed/.*': 'emailConfirmed'
And then I retrieve the params like this in emailConfirmed:
var hash = window.location.hash.split('/');
hash.shift();
// hash[0] => email
// hash[1] => first

Categories

Resources