EDIT 2
I accidently communicated that the exception was of type IndexOutOfBounds but it is actually of type IndexOutOfRange.
I'm trying to save some objects to a server. These are the actions that are taken:
The javascript array is an array of Text objects.
These Text objects are put inside a TextModel list when they are transfered to the controller. (by using JQuery.Post)
Each element in the TextModels list then needs to be put into a new server object, TextInfo.
Each TextInfo object is put into a new array that's stored in a PageInfo object
The PageInfo object is send to the server for further processing.
The process errors on #3. For some reason I get an IndexOutOfRange exception when looping through the TextModel list. After all TextModel list elements are added (looped through) to the new TextInfo list, the error comes. This when it tries to increase the for-loops counter (i). I expect it to increase i and then exit the loop when the condition isn't met but it seemingly errors on i++.
I have no idea what could be causing this error. As seen in the code I only use the listed objects to fill the properties of the new object, I don't try to edit the properties while in the loop. Any insight as to what could cause the problem would be helpful. Thanks in advance.
Edit 1
Apparently this wasn't clear enough. All objects send with the post are received as expected in the controller. Count always holds the expected amount of elements and all elements are properly set. See JSON example & Image 1 for details.
Text object as defined in Javascript (#1)
function Text() {
//this.Id = 0;
this.text = "Hallo";
this.x = 0;
this.y = 0;
this.fillColor = "black";
this.fontFamily = "Arial";
this.fontStyle = "normal";
this.fontSize = "18pt";
this.fontWeight = "normal";
this.textAlignment = "start";
this.angle = 0;
}
function addText(text, fillColor, fontSize, fontFamily, fontStyle, fontWeight, textAlignment, x, y, angle) {
var textTemp = new Text;
textTemp.text = multilineText(text, fontSize, fontFamily, fontStyle);
textTemp.fillColor = fillColor;
textTemp.fontSize = fontSize;
textTemp.fontFamily = fontFamily;
textTemp.fontWeight = fontWeight;
textTemp.fontStyle = fontStyle;
textTemp.x = x;
textTemp.y = y;
textTemp.textAlignment = textAlignment;
textTemp.angle = angle;
textObjects.push(textTemp);
}
Javascript JQuery.Post & TextModel in Controller (#2)
// Saves page information
// Called on Save, Next / Previous Page.
// #param curPageNum The currently selected page's number.
function savePage(curPageNum) {
var pageNumber = curPageNum;
var json = JSON.stringify({ TextObjects: textObjects, ImageSource: imageSource, CurrentPageNumber: pageNumber });
alert(json);
$.ajax({
url: "/NextprintPhotobook/SavePageInfo",
type: "POST",
data: json,
contentType: 'application/json; charset=utf-8',
success: function (data) {
console.log(data.message);
showSuccess(data.message);
},
error: function (data) {
console.log(data);
showError("Er is een fout opgetreden bij het opslaan van de foto.");
}
});
}
Controller (#3, #4)
public ActionResult SavePageInfo(List<TextModel> TextObjects, string ImageSource, int CurrentPageNumber)
{
try
{
using (HttpClient client = new HttpClient())
{
#region Define & initialize textInfoArray (Array of Textinfo objects)
// textInfoArray used to set property of the page info object.
TextInfo[] textInfoArray = null;
List<TextInfo> textInfoList = new List<TextInfo>();
if (TextObjects != null)
{
// Build textInfo list.
// textInfoList used to build a list from the TextObjects parameter.
for (int i = 0; i < TextObjects.Count; i++)
{
TextModel textObject = TextObjects[i];
PositionInfo positionInfo = null;
if (textObject.x >= 0 && textObject.y >= 0)
{
int xTemp = textObject.x;
int yTemp = textObject.y;
positionInfo = new PositionInfo()
{
X = xTemp,
Y = yTemp
};
}
string textTemp = textObject.text;
string fontFamilyTemp = textObject.fontFamily;
string fontSizeTemp = textObject.fontSize;
string fontWeightTemp = textObject.fontWeight;
string fontStyleTemp = textObject.fontStyle;
string fillColorTemp = textObject.fillColor;
int rotationTemp = textObject.angle;
string alignmentTemp = textObject.textAlignment;
TextInfo textInfo = new TextInfo()
{
Value = textTemp,
Font = fontFamilyTemp,
Size = fontSizeTemp,
Weight = fontWeightTemp,
Style = fontStyleTemp,
Color = fillColorTemp,
Rotation = rotationTemp,
Position = positionInfo,
Alignment = alignmentTemp
};
textInfoList.Add(textInfo);
}
textInfoArray = textInfoList.ToArray();
}
#endregion
#region Define & initialize PageInfo
PageInfo pageInfo = null;
if (textInfoList == null)
{
return Json(new APIResponse { OK = 0, message = "Er is een onbekende fout opgetreden binnen de API." }, "text/html");
}
string fileName = "";
string tempSource = ImageSource;
fileName = this.getFileNameFromSource(tempSource);
if (fileName == null)
{
return Json(new APIResponse { OK = 0, message = "Er is een onbekende fout opgetreden binnen de API." }, "text/html");
}
if (CurrentPageNumber <= 0)
{
return Json(new APIResponse { OK = 0, message = "Er is een onbekende fout opgetreden binnen de API." }, "text/html");
}
pageInfo = new PageInfo()
{
PageNumber = CurrentPageNumber,
FileName = fileName,
Text = textInfoArray
};
#endregion
client.BaseAddress = new Uri(SERVER_URI);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.PostAsJsonAsync<PageInfo>("api/Xml/SavePageInfo?hash=" + SecurityHash + "&collectionId="
+ CollectionId + "&version=" + Version, pageInfo).Result;
if (response.IsSuccessStatusCode)
{
APIResponse apiResponse = response.Content.ReadAsAsync<APIResponse>().Result;
Logger.log("API response received, see API log for more details.");
return Json(apiResponse, "text/html");
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}
catch (Exception e)
{
Logger.log("An exception occurred while saving page info: {0}", e.Message);
return Json(new APIResponse { OK = 0, message = "Er is een onbekende fout opgetreden bij het communiceren met de API." }, "text/html");
}
}
JSON Example
This is the JSON that's send:
[{"TextObjects":[{"text":"Test
","x":50,"y":50,"fillColor":"#77DD44","fontFamily":"arial","fontStyle":"normal","fontSize":"18pt","fontWeight":"normal","textAlignment":"start","angle":0}],"ImageSource":"http://localhost:22223//Cyber/1718/1/img/resized/1.jpg","CurrentPageNumber":1}]
C# received object
Related
I have a Flask python application where the user can select a vehicle in the web page and depending on which vehicle is selected, the application should propose different WMS layers in the Leaflet layer control that the user can tick on / off depending of his needs. The logic of feeding new layers depending of vehicle selection is done via ajax, where the layers vs vehicle is fetched from a database.
This works fine the first time the vehicle is selected, however, when a different vehicle is selected and then the first vehicle again, the WMS layer does not show in the leaflet map, though the layer name can be selected in the layer control. To avoid having too many checkboxes the vessels has been added to the same marker group so the user only have the vessel group and a couple WMS layers to tick on/off.
I'm sure the solution is dead simple. However have been exhausing 'every' tip I could find on different forums.
JavaScript:
var vesselVar = null;
var mapVessel = null;
var layerControl = null;
var baseMaps = null;
var overlayMaps = null;
var layerNameArr = null;
var layerLinkArr = null;
var baseMapArr = null;
var myVessels = null;
var sLayerName = null;
var sUrl = null;
var wmsOptions = null;
var myWmsLayer = null;
function myOnSelectedVessel() {
let sSelectedVessel = $('#sSelectedVessel').find(":selected").text()
if (sSelectedVessel.length > 0) {
$.ajax({
url: '/vessel_map_set_vessel_id',
data: {
'sSelectedVesselName': sSelectedVessel,
},
type: 'POST',
dataType: 'json',
success: function (response) {
if (response.result) {
alert(response.msg);
}
myCreateOverlay(response.liVessels, response.liLastVesselPos, response.arrLeaflet.toString());
myCreateBaseLayers();
myCreateMap(response.lat, response.lon);
mapVessel.panTo(new L.LatLng(parseFloat(response.lat), parseFloat(response.lon)));
},
error: function (error) {
alert("error in function vessel_map.myOnSelectedVessel");
}
});
}
}
function myCreateVesselGroup(liVessels, liLastVesselPos) {
try {
myVessels = null;
vesselVar = null;
myVessels = L.layerGroup([]);
vesselVar = [];
let iLen = liLastVesselPos.length;
const arrVesselNames = liVessels.toString().split(',');
const arrVesselPos = liLastVesselPos.toString().split(',');
let iCntVesselPos = 0;
for (let iCn7 = 0; iCn7 < iLen; iCn7++) {
arrPos = liLastVesselPos[iCn7];
//alert(arrVesselNames[iCn7]);
myAddVesselMarker(arrVesselNames[iCn7], arrVesselPos[iCntVesselPos], arrVesselPos[iCntVesselPos + 1]);
myVessels.addLayer(vesselVar[iCn7]);
iCntVesselPos += 2;
}
}
catch (err) {
alert("Error in function myCreateBaseLayers: " + err.message);
}
}
function myCreateOverlay(liVessels, liLastVesselPos, sLeafletWmsString) {
try {
if (mapVessel !== null) {
if ((overlayMaps !== null) && (layerControl !== null)) {
mapVessel.eachLayer(function (layer) {
mapVessel.removeLayer(layer);
});
}
}
overlayMaps = null;
overlayMaps = {};
myCreateVesselGroup(liVessels, liLastVesselPos)
overlayMaps['Vessels'] = myVessels;
if (sLeafletWmsString.length > 1) {
const arrLeaflet2 = sLeafletWmsString.split(',')
let iLen = arrLeaflet2.length;
for (let iCnt4 = 0; iCnt4 < iLen; iCnt4 += 2) {
sLayerName = null;
sLayerName = arrLeaflet2[iCnt4].toString();
wmsOptions = null;
wmsOptions = {};
wmsOptions.layers = sLayerName;
wmsOptions.format = 'image/png';
wmsOptions.transparent = true;
wmsOptions.opacity = 0.5;
sUrl = null;
sUrl = arrLeaflet2[iCnt4 + 1];
myWmsLayer = null
myWmsLayer = new L.tileLayer.wms(sUrl, wmsOptions);
overlayMaps[sLayerName] = myWmsLayer;
}
}
}
catch (err) {
alert("Error in function myCreateOverlay: " + err.message);
}
}
function myCreateBaseLayers() {
try {
layerNameArr = null;
layerLinkArr = null;
baseMaps = null;
layerNameArr = [];
layerLinkArr = [];
baseMaps = []
layerNameArr.push('OpenStreetMap');
layerLinkArr.push(L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: 'OpenStreetMap', transparent: true }));
for (iCn2 = 0; iCn2 < layerLinkArr.length; iCn2++) {
baseMaps[layerNameArr[iCn2]] = layerLinkArr[iCn2];
}
}
catch (err) {
alert("Error in function myCreateBaseLayers: " + err.message);
}
}
function myAddVesselMarker(sTitle, dLat, dLon) {
try {
var markerOptions = {};
markerOptions.title = sTitle;
markerOptions.clickable = true;
markerOptions.draggable = false;
var sPopupText = "Lat: " + dLat.toString() + ", Lon: " + dLon.toString();
vesselVar.push(L.marker([dLat, dLon], markerOptions).bindPopup(sPopupText));
}
catch (err) {
alert("Error in function myAddVesselMarker: " + err.message);
}
}
function myCreateMap(dLatCenter, dLonCenter) {
try {
if (mapVessel !== null) {
mapVessel.invalidateSize();
mapVessel.off();
mapVessel.remove();
}
mapVessel = null;
layerControl = null;
mapVessel = new L.map('map', { center: [dLatCenter, dLonCenter], zoom: 13, layers: [layerLinkArr[0]] });
layerControl = new L.control.layers(baseMaps, overlayMaps).addTo(mapVessel);
}
catch (err) {
alert("Error in function myCreateMap: " + err.message);
}
}
For anybody else that struggles with the same issue. My problem was that the '&' character in the url string is a javascript entity character that is interpreted as javascript code (even though the url string was a varaiable and not in the JS code itself). For the first on load draw I used Flask's Jinja2, which automatically modified all the '&' characters in the url string, hence why the first time draw showed up ok. Once I replaced all the '&' characters in the ajax string, that I sent back to javascript when the user changed a vehicle, with '&', the overlay showed up also when I changed the vehicle.
I decided to build a high/low game in javascript and am running into an issue where the numbers displayed are ahead of what the variables have stored or the exact opposite. I can't seem to get them to match.
EDIT: I figured it out, the code ran before ajax was done causing an offset.
It helps me more when I find answers with the old code to compare with the new so I'll leave the broken code. Updated with working code at the end.
Page that helped me figure out a fix:
Wait for AJAX before continuing through separate function
Original JavaScript:
var y = "0";
var z = "0";
var output_div = document.getElementById("outcome");
var last_ = document.getElementById("val");
var cardVal;
function higher(x) {
var new_ = last_.innerHTML; //getting new value
y = last_.getAttribute("data-old"); //getting old value
console.log("data_old " + y);
z = ajx(); //calling function return the value from which need to compare
console.log("data_new " + z);
if (x === 1) {
if (z > y) {
output_div.innerHTML = "Winner!";
} else {
output_div.innerHTML = "Loser!";
}
} else {
if (z < y) {
output_div.innerHTML = "Winner!";
} else {
output_div.innerHTML = "Loser!";
}
}
last_.setAttribute("data-old", new_); //setting old value with current value of div
}
function ajx() {
$.ajax({
url: "./getfacecard.php",
success: function(response) {
var result = $.parseJSON(response);
var img = result[0];
cardVal = result[1];
document.getElementById(\'card\').src = img;
document.getElementById(\'val\').innerHTML = cardVal;
}
});
return cardVal; // return current card value in calling function
}
Updated Working JavaScript:
var lastVal = document.getElementById("lastVal"); //Last played cars value
var wl = document.getElementById("outcome"); //Shows win or lose
var newVal = document.getElementById("currentVal"); //Current face up card
var iSrc = document.getElementById("card"); //Card img
var lVal; //Last cards value from post
var iLink; //Image link from post
var nVal; //Gets new html to be sent to post.
function start(x){
// console.log("Start:");
ajx(function(){ //Runs ajax before continuing
iSrc.src = iLink; //Set new card image src
newVal.innerHTML = nVal; //Sets Current card value in div
lastVal.innerHTML = lVal; //Sets Last card value in div
// console.log("-slgn"); //Consoles to track code launch order.
// console.log("-Last Value: "+lVal);
// console.log("-Current Value: "+nVal);
// console.log("-Link: "+iLink);
// console.log(x);
if(x===1){ //If clicked higher
if(nVal>lVal){ //If new card is higher than old card
wl.innerHTML = "Winner!";
}else{
wl.innerHTML = "Loser!"
}
}
if(x===2){
if(nVal<lVal){ //If new card is lower than old card
wl.innerHTML = "Winner!";
}else{
wl.innerHTML = "Loser!"
}
}
});
}
function ajx(callback) {
$.ajax({
type: "POST",
data: {data:newVal.innerHTML}, //Post new card value to be returned as last card.
url: "./getfacecard.php",
success: function(response) {
var result = $.parseJSON(response);
iLink = result[0]; //img
lVal = result[2]; //Last card
nVal = result[1]; //New Card
// console.log("ajax");
callback(); //Go back and the code
}
});
}
You can use custom attribute in your div to save your current value as old value and vice versa so only one div required here i.e: Your div look like below :
<div data-old="0" id="val">0</div>
And js code will look like below:
var y = "0";
var z = "0";
var output_div = document.getElementById("outcome");
var last_ = document.getElementById("val");
function higher(x) {
var new_ = last_.innerHTML; //getting new value
y = last_.getAttribute("data-old"); //getting old value
console.log("data_old " + y);
z = ajx(); //calling function return the value from which need to compare
console.log("data_new " + z);
if (x === 1) {
if (z > y) {
output_div.innerHTML = "Winner!";
} else {
output_div.innerHTML = "Loser!";
}
} else {
if (z < y) {
output_div.innerHTML = "Winner!";
} else {
output_div.innerHTML = "Loser!";
}
}
last_.setAttribute("data-old", new_); //setting old value with current value of div
}
function ajx() {
$.ajax({
url: "./getfacecard.php",
success: function(response) {
var result = $.parseJSON(response);
var img = result[0];
var cardVal = result[1];
document.getElementById('card').src = img;
document.getElementById('val').innerHTML = cardVal;
}
});
return cardVal; // return current card value in calling function
}
In above js code what i done is after ajax call finishes execution it will return cardVal which will get pass in variable z and then we will compare it with y i.e : old value and print required output.Also, i have return value from ajax called because when you do document.getElementById(\'val\').innerHTML = cardVal; still this value is not available with us in our function higher so to overcome this i have return that value to your calling function.
(This code is already tested and working as excepted )
I don't know if this is a glitch or a mistake that I made. But I use console.log(record) this is the result
accountRateId: 6
effectiveEndDate: "2019-09-30T00:00:00"
effectiveStartDate: "2019-09-01T00:00:00"
rate: 100
I have this code to assign the variables
let rate = record.rate;
let effectiveStartDate = record.effectiveStartDate;
let effectiveEndDate = record.effectiveEndDate;
console.log(rate);
but console.log(rate) returns undefined. Can anyone help me with this?
Update:
This is the code from the api
[Authorize("ADMIN")]
[HttpGet("Update/{id}")]
public IActionResult GetUpdate([FromQuery]QueryPagingParametersForNotes inParameters, int id)
{
List<object> rateList = new List<object>();
var rateListQueryResults = Database.AccountRates.Where(rate => rate.AccountRateId == id);
foreach (var oneRate in rateListQueryResults)
{
rateList.Add(new
{
accountRateId = oneRate.AccountRateId,
rate = oneRate.RatePerHour,
effectiveStartDate = oneRate.EffectiveStartDate,
effectiveEndDate = oneRate.EffectiveEndDate,
});
}
return new JsonResult(rateList);
}
This is the the code in javascript
$.ajax({
method: 'GET',
url: '/API/AccountRates/Update/' + inId
}).done(function (data) {
let record = data;
console.log(record);
let rate = record.rate;
let effectiveStartDate = record.effectiveStartDate;
let effectiveEndDate = record.effectiveEndDate;
console.log(rate);
console.log(effectiveStartDate);
//Populate the respective input controls with the lesson details.
$('#rateInput').val(rate);
$('#startDateInput').val(effectiveStartDate);
$('#endDateInput').val(effectiveEndDate);
})
I have a callable function that should return a value, but the only thing ever returned is null. Below is the current version of the function. I have also tried having a return on the first promise (the original once call), and at the end in another then returning the GUID. It actually returned data in that case, but it returned immediately and the GUID was empty.
How can I accomplish my goal and still return the GUID? I don't know when the function is called if I will use a new GUID that I generate, or one that already exists in the database.
There is a similar question here: Receiving returned data from firebase callable functions , but in that case it was because he never returned a promise from the function. I am returning a promise on all code paths. Unless I have to return the initial promise from the once call? In which case, how can I return the GUID when I don't know it yet?
I am also trying to throw an error in a couple of places and the error shows up in the logs for the function, but is never sent to the client that called the function.
I am going off of the examples here: https://firebase.google.com/docs/functions/callable
Sorry for the code bomb.
Calling the function:
var newGame = firebase.functions().httpsCallable('findCreateGame');
newGame({}).then(function(result) {
// Read result of the Cloud Function.
//var sGameID = result.data.guid;
console.log(result);
}).catch(function(error) {
console.log(error);
});
Function:
exports.findCreateGame = functions.https.onCall((data, context) => {
console.log("findCurrentGame Called.")
/**
* WHAT NEEDS DONE
*
*
* Pull in user's information
* Determine their win/loss ratio and search for a game using transactions in either low medium or high queue
* If there are no open games in their bracket, search the one above, then below
* If no open games anywhere, create a new game in their bracket
* If an open game is found, write the UID to the game and add the game's ID to the user's profile
*
*/
var uid = context.auth.uid;
var section = "";
var sUsername = "";
var sProfilePic = "";
var currentGames = null;
var sGUID = "";
//Get the user's info
var userref = admin.database().ref('users/' + uid);
userref.once("value", function(data) {
var ratio = 0;
var wins = parseInt(data.val().wins);
var losses = parseInt(data.val().losses);
var lives = parseInt(data.val().lives);
if (lives < 1){
//This user is out of lives, should not have been able to get here
//Throw an exception so that we can see why it failed
throw new functions.https.HttpsError('permission-denied', 'You do not have enough lives to start a new game.');
}
sUsername = data.val().username;
sProfilePic = data.val().profilepicture;
//Handle if they have no losses
if (losses == 0){
ratio = 100;
} else {
ratio = (wins / losses) * 100;
}
//If they have played less than 5 games, put them in noob tier
if (wins + losses < 5){
ratio = 0;
}
if (ratio <= 33){
section = "noob";
} else if (ratio > 33 && ratio <= 66){
section = "average";
} else {
section = "expert";
}
}).then(() => {
//Get all of the games this user is currently in
admin.database().ref('games').orderByChild(uid).once('value', function(data) {
currentGames = data.val();
}).then(() => {
//Generate a new GUID in case we need to set up a new game
sGUID = '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);
});
var queueref = admin.database().ref('gamequeue/' + section);
queueref.transaction(function(currentGUID) {
if (currentGUID == null){
//Write our GUID in the queue
return sGUID;
} else {
//Get the id of the game we just got
sGUID = currentGUID
return null;
}
}).then((res) => {
if (res.snapshot.val() != null){
//This means we are creating the game lobby
//Generate a new answer
var newAnswer = "";
while (newAnswer.length < 4){
var temp = Math.floor(Math.random() * 9) + 1;
temp = temp.toString();
if (!newAnswer.includes(temp)){
newAnswer += temp;
}
}
var obj = {username: sUsername, score: 0, profilepicture: sProfilePic};
return admin.database().ref('games/' + sGUID).set({id: sGUID, requestor: uid, [uid]: obj, answer: newAnswer, turn: uid, status: 'pending'}).then(() => {
return {guid: sGUID};
});
} else {
//We found a game to join
//If we are in a duplicate request situation, make sure the GUID is a string
if (typeof(sGUID) != 'string'){
sGUID = Object.keys(sGUID)[0];
}
//Make sure we didn't find our own game request
if (currentGames[sGUID] != null){
//Add this GUID back to the queue, we shouldn't have removed it
return admin.database().ref('gamequeue/' + section + '/' + sGUID).set('');
//Throw an exception that says you can only have one open game at a time
throw new functions.https.HttpsError('already-exists', 'We are still finding a match for your last request. You are only allowed one open request at a time.');
} else {
//Get the current game info
admin.database().ref('games/' + sGUID).once('value', function(data) {
var sRequestor = data.val().requestor;
var sOpponentUsername = data.val()[sRequestor].username;
var sOpponentProfilePic = data.val()[sRequestor].profilepicture;
//Write all of our info to the game
return admin.database().ref('games/' + sGUID).update({[sRequestor]: {opponentusername: sUsername, opponentprofilepicture: sProfilePic}, [uid]: {username: sUsername, score: 0, opponentusername: sOpponentUsername, opponentprofilepicture: sOpponentProfilePic}, status: 'active'}).then(() => {
return {guid: sGUID};
});
});
}
}
});
});
})
});
The documentation for callable functions explains:
To return data after an asynchronous operation, return a promise. The
data returned by the promise is sent back to the client.
You have many asynchronous operations that must be chained together. A return needs to be added to each of these statements (as shown):
return userref.once("value", function(data) {...
return admin.database().ref('games').orderByChild(uid).once('value', function(data) {...
return queueref.transaction(function(currentGUID) {...
return admin.database().ref('games/' + sGUID).once('value', function(data) {...
I am trying to desteghide an image to reveal the secret message.
I have got it working, but only by browsing the image which contains the message.
If you take a look at the screenshot:
To make this work I need to save the image (the arsenal badge). Then use the browse to upload the image again to make this work. I want to skip the browse stage and make automatically by grabbing the image straight from the div.
Any ideas?
here is my code (its quite messy sorry)
window.onload = function() {
// add action to the file input
var input = document.getElementById('file');
input.addEventListener('change', importImage);
};
var maxMessageSize = 1000;
$("#desteg").click(function()
{
decode();
});
var importImage = function(e) {
var reader = new FileReader();
reader.onload = function(event) {
// set the preview
document.getElementById('preview').style.display = 'block';
document.getElementById('preview').src = event.target.result;
// wipe all the fields clean
document.getElementById('pass').value = '';
document.getElementById('ContentArea').innerHTML = '';
// read the data into the canvas element
var img = new Image();
img.onload = function() {
var ctx = document.getElementById('canvas').getContext('2d');
ctx.canvas.width = img.width;
ctx.canvas.height = img.height;
ctx.drawImage(img, 0, 0);
decode();
};
img.src = event.target.result;
};
reader.readAsDataURL(e.target.files[0]);
};
// encode the image and save it
// decode the image and display the contents if there is anything
var decode = function() {
var password = document.getElementById('pass').value;
var passwordFail = 'Password is incorrect or there is nothing here.';
// decode the message with the supplied password
var ctx = document.getElementById('canvas').getContext('2d');
var imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
var message = decodeMessage(imgData.data, sjcl.hash.sha256.hash(password));
// try to parse the JSON
var obj = null;
try {
obj = JSON.parse(message);
} catch (e) {
// display the "choose" view
if (password.length > 0) {
alert(passwordFail);
}
}
// display the "reveal" view
if (obj) {
// decrypt if necessary
if (obj.ct) {
try {
obj.text = sjcl.decrypt(password, message);
} catch (e) {
alert(passwordFail);
}
}
// escape special characters
var escChars = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
'\'': ''',
'/': '/',
'\n': '<br/>'
};
var escHtml = function(string) {
return String(string).replace(/[&<>"'\/\n]/g, function (c) {
return escChars[c];
});
};
document.getElementById('ContentArea').innerHTML = escHtml(obj.text);
}
};
// returns a 1 or 0 for the bit in 'location'
var getBit = function(number, location) {
return ((number >> location) & 1);
};
// sets the bit in 'location' to 'bit' (either a 1 or 0)
var setBit = function(number, location, bit) {
return (number & ~(1 << location)) | (bit << location);
};
// returns an array of 1s and 0s for a 2-byte number
var getBitsFromNumber = function(number) {
var bits = [];
for (var i = 0; i < 16; i++) {
bits.push(getBit(number, i));
}
return bits;
};
// returns the next 2-byte number
var getNumberFromBits = function(bytes, history, hash) {
var number = 0, pos = 0;
while (pos < 16) {
var loc = getNextLocation(history, hash, bytes.length);
var bit = getBit(bytes[loc], 0);
number = setBit(number, pos, bit);
pos++;
}
return number;
};
// returns an array of 1s and 0s for the string 'message'
var getMessageBits = function(message) {
var messageBits = [];
for (var i = 0; i < message.length; i++) {
var code = message.charCodeAt(i);
messageBits = messageBits.concat(getBitsFromNumber(code));
}
return messageBits;
};
// gets the next location to store a bit
var getNextLocation = function(history, hash, total) {
var pos = history.length;
var loc = Math.abs(hash[pos % hash.length] * (pos + 1)) % total;
while (true) {
if (loc >= total) {
loc = 0;
} else if (history.indexOf(loc) >= 0) {
loc++;
} else if ((loc + 1) % 4 === 0) {
loc++;
} else {
history.push(loc);
return loc;
}
}
};
// encodes the supplied 'message' into the CanvasPixelArray 'colors'
// returns the message encoded in the CanvasPixelArray 'colors'
var decodeMessage = function(colors, hash) {
// this will store the color values we've already read from
var history = [];
// get the message size
var messageSize = getNumberFromBits(colors, history, hash);
// exit early if the message is too big for the image
if ((messageSize + 1) * 16 > colors.length * 0.75) {
return '';
}
// exit early if the message is above an artificial limit
if (messageSize === 0 || messageSize > maxMessageSize) {
return '';
}
// put each character into an array
var message = [];
for (var i = 0; i < messageSize; i++) {
var code = getNumberFromBits(colors, history, hash);
message.push(String.fromCharCode(code));
}
// the characters should parse into valid JSON
return message.join('');
};