How to mock cookies with QUnit and Sinon? - javascript

I have this code:
var CookieHelper = function () {
return {
GetCookie : function (cookieName) {
var cookieNameRequest = cookieName + "=";
var cookiesCollection = document.cookie.split(";");
for (var i = 0; i < cookiesCollection.length; i++) {
var cookieValuePair = cookiesCollection[i];
while (cookieValuePair.charAt(0) == " ") cookieValuePair = cookieValuePair.substring(1, cookieValuePair.length);
if (cookieValuePair.indexOf(cookieNameRequest) == 0)
return cookieValuePair.substring(cookieNameRequest.length, cookieValuePair.length);
}
return null;
},
DeleteCookie : function (cookieName) {
CookieHelper.SetCookie(cookieName, "", -1);
},
SetCookie : function (cookieName, cookieValue, cookieExpirationDays) {
var tmpDate = new Date;
if (cookieExpirationDays) {
tmpDate.setTime(tmpDate.getTime() + cookieExpirationDays * 24 * 60 * 60 * 1000);
var expires = "; expires=" + tmpDate.toGMTString();
} else {
// if cookieExpirationDays isn't set cookie will expire at the end of the day
var expirationTime = new Date(tmpDate.getFullYear(), tmpDate.getMonth(), tmpDate.getDate(), 23, 59, 59);
var expires = "; expires=" + expirationTime.toGMTString();
}
document.cookie = cookieName + "=" + cookieValue + expires + "; path=/;" + (location.protocol === "https:" ? "secure=true" : "");
}
};
}();
and I need to write unit tests for GetCookie.
I tried with:
QUnit.module('Cookie Helper Tests');
QUnit.test('GetCookie - returns no cookie', function(assert) {
var stub = sinon.spy(CookieHelper, 'GetCookie');
var cookieName = 'testCookieName';
var cookieValue = CookieHelper.GetCookie(cookieName);
assert.ok(cookieValue == null, 'returns no cookie');
});
I need to mock/stub document.cookie but I am completely new with Sinon and QUnit.
Can someone give me explanation what I did wrong?
Thanks in advance!
Srdjan

Well actually you can't mock properties with Sinon and more over you cannot mock the document.cookies property as it's immutable. What you can do though, is to use a fake double object that mimics the behavior of the original object, as described here.
For example, if you were running the same test outside of the browser (e.g. in NodeJS) you would do something like that
const chai = require('chai');
const sinon = require('sinon');
const SinonChai = require('sinon-chai');
chai.use(SinonChai);
chai.should();
context('test', function() {
it('should pass',
(done) => {
var cookieName = 'testCookieName';
document = {
cookie: cookieName + '=123'
};
var cookieValue = CookieHelper.GetCookie(cookieName);
console.log(cookieValue);
done();
});
});
in order to introduce a document object to the context and mimic it's cookies attribute by returning a fixed result.
Now, there is a workaround for the browser which involves redefining the getter and setter of 'document.cookie' as described here:
(function (document) {
var cookies = {};
document.__defineGetter__('cookie', function () {
var output = [];
for (var cookieName in cookies) {
output.push(cookieName + '=' + cookies[cookieName]);
}
return output.join(';');
});
document.__defineSetter__('cookie', function (s) {
var indexOfSeparator = s.indexOf('=');
var key = s.substr(0, indexOfSeparator);
var value = s.substring(indexOfSeparator + 1);
cookies[key] = value;
return key + '=' + value;
});
document.clearCookies = function () {
cookies = {};
};
})(document);

Related

Having trouble setting cookies using Javascript

I'm working on setting cookies for a website and I've been having trouble getting it right. When I check DevTools on my browser (Chrome), I'm always getting messages about the SameSite attribute being unspecified. Would anyone be able to help out?
const dropCookie = true;
const cookieDays = 14;
const cookieName = "Compliance";
const cookieState = "on";
const banner = document.getElementById("cookie-banner");
const displayBanner = () => {
const main = document.getElementsByTagName("main")[0];
banner.style.display = "flex";
main.className += " cookie-banner";
createCookie(window.cookieName, window.cookieState, window.cookieDays);
}
const createCookie = (name, value, days) => {
let cookie = name + "=" + encodeURIComponent(value);
if (typeof days === "number") {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
const expires = "expires=" + date.toUTCString();
}
if (window.dropCookie) {
cookie = `${name}=${value}; ${expires}; path=/; secure; samesite=none`
document.cookie = cookie;
}
}
function checkCookie(name) {
const nameEq = `${name}=`;
const cookieArr = document.cookie.split(";");
for (let i = 0; i < cookieArr.length; i++) {
let c = cookieArr[i];
while (c.charAt(0) == " ") c = c.substring(1, c.length);
if (c.indexOf(nameEq) == 0) return c.substring(nameEq.length, c.length);
}
return null;
}
function eraseCookie(name) {
createCookie(name, "", -1);
}
window.onload = () => {
if (checkCookie(window.cookieName) != window.cookieState) {
displayBanner();
}
}
I found that there were several reference errors being thrown from my code. I had to correct the instances where I referenced variables as properties of window, and also fix the Unix to UTC time conversion in createCookie.
const dropCookie = true;
const cookieDays = 14;
const cookieName = "compliance";
const cookieState = "on";
const banner = document.getElementById("cookie-banner");
const displayBanner = () => {
const main = document.getElementsByTagName("main")[0];
banner.style.display = "flex";
main.className += " cookie-banner";
createCookie(cookieName, cookieState, cookieDays);
}
const closeBanner = () => {
banner.style.display = "none";
}
const createCookie = (name, value, days) => {
let cookie = name + "=" + encodeURIComponent(value);
if (typeof days === "number") {
let date = new Date();
let unixDate = date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
date = new Date(unixDate);
const expires = "expires=" + date.toUTCString();
if (dropCookie) {
document.cookie = `${name}=${value}; ${expires}; path=/; secure; samesite=lax`
}
}
}
Lastly, I fixed the window.onload statement by saving the function to a variable windowInit, then calling it in window.onload.
const windowInit = () => {
if (checkCookie(cookieName) != cookieState) {
displayBanner();
}
}
window.onload = windowInit();

Javascript issue with precedence

I have built this function to replace a group of characters in a string by a random value from another list within a function:
function replaceExpr(a) {
var expToReplace = 0
var newSent = a
while (expToReplace == 0) {
if (a.search("zx") == -1) {
expToReplace = 1
} else {
var startPos = a.search("zx");
startPos += 2;
var endPos = a.search("xz");
var b = a.substring(startPos, endPos);
var fn = window[b];
if (typeof fn === "function") var newWord = fn();
final = newSent.replace("zx" + b + "xz", newWord);
newSent = final
a = a.replace("zx" + b + "xz", "")
}
}
return final
}
function appearance() {
var list = [
"attractive",
"fit",
"handsome",
"plain",
"short",
"tall",
"skinny",
"well-built",
"unkempt",
"unattractive"
]
return list[Math.floor(Math.random() * list.length)];
}
function personality() {
var list = [
"aggresive",
"absent-minded",
"cautious",
"detached from the real world",
"easygoing",
"focused",
"honest",
"dishonest",
"polite",
"uncivilized"
]
return list[Math.floor(Math.random() * list.length)];
}
An example :
var a = replaceExpr("Theodor is a zxappearancexz man. He seems rather zxpersonalityxz.")
alert(a)
// Theodor is a unattractive man. He seems rather cautious.
Everything works perfectly with the function but I have an issue related to it. As you can see, there's one grammar mistake : it's written "a unattractive" where it should be "an unattractive".
There's a function I usually use to to fix the a\an issue which is :
var AvsAnSimple = (function (root) {
//by Eamon Nerbonne (from http://home.nerbonne.org/A-vs-An), Apache 2.0 license
// finds if a word needs a "a" or "an" before it
var dict = "2h.#2.a;i;&1.N;*4.a;e;i;o;/9.a;e;h1.o.i;l1./;n1.o.o;r1.e.s1./;01.8;12.1a;01.0;12.8;9;2.31.7;4.5.6.7.8.9.8a;0a.0;1;2;3;4;5;6;7;8;9;11; .22; .–.31; .42; .–.55; .,.h.k.m.62; .k.72; .–.82; .,.92; .–.8;<2.m1.d;o;=1.=1.E;#;A6;A1;A1.S;i1;r1;o.m1;a1;r1; .n1;d1;a1;l1;u1;c1.i1.a1.n;s1;t1;u1;r1;i1;a1;s.t1;h1;l1;e1;t1;e1.s;B2.h2.a1.i1;r1;a.á;o1.r1.d1. ;C3.a1.i1.s1.s.h4.a2.i1.s1;e.o1.i;l1.á;r1.o1.í;u2.i;r1.r1.a;o1.n1.g1.j;D7.a1.o1.q;i2.n1.a1.s;o1.t;u1.a1.l1.c;á1. ;ò;ù;ư;E7;U1;R.b1;o1;l1;i.m1;p1;e1;z.n1;a1;m.s1;p5.a1.c;e;h;o;r;u1.l1;o.w1;i.F11. ;,;.;/;0;1;2;3;4;5;6;71.0.8;9;Ae;B.C.D.F.I2.L.R.K.L.M.N.P.Q.R.S.T.B;C1;M.D;E2.C;I;F1;r.H;I3.A1;T.R1. ;U;J;L3.C;N;P;M;O1. ;P1;..R2.A1. ;S;S;T1;S.U2.,;.;X;Y1;V.c;f1.o.h;σ;G7.e1.r1.n1.e;h1.a3.e;i;o;i1.a1.n1.g;o2.f1. ;t1.t1. ;r1.i1.a;w1.a1.r1.r;ú;Hs. ;&;,;.2;A.I.1;2;3;5;7;B1;P.C;D;F;G;H1;I.I6;C.G.N.P.S1.D;T.K1.9;L;M1;..N;O2. ;V;P;R1;T.S1.F.T;V;e2.i1.r;r1.r1.n;o2.n6;d.e1.s;g.k.o2;l.r1;i1.f;v.u1.r;I3;I2;*.I.n1;d1;e1;p1;e1;n1;d2;e1;n1;c1;i.ê.s1;l1;a1;n1;d1;s.J1.i1.a1.o;Ly. ;,;.;1;2;3;4;8;A3. ;P;X;B;C;D;E2. ;D;F1;T.G;H1.D.I1.R;L;M;N;P;R;S1;m.T;U1. ;V1;C.W1.T;Z;^;a1.o1.i1.g;o1.c1.h1.a1;b.p;u1.s1.h1;o.ộ;M15. ;&;,;.1;A1;.1;S./;1;2;3;4;5;6;7;8;Ai;B.C.D.F.G.J.L.M.N.P.R.S.T.V.W.X.Y.Z.B1;S1;T.C;D;E3.P1;S.W;n;F;G;H;I4. ;5;6;T1;M.K;L;M;N;O1.U;P;Q;R;S;T1;R.U2. ;V;V;X;b1.u1.m;f;h;o2.D1.e.U1;..p1.3;s1.c;Ny. ;+;.1.E.4;7;8;:;A3.A1;F.I;S1.L;B;C;D;E3.A;H;S1. ;F1;U.G;H;I7.C.D1. ;K.L.N.O.S.K;L;M1;M.N2.R;T;P1.O1.V1./1.B;R2;J.T.S1;W.T1;L1.D.U1.S;V;W2.A;O1.H;X;Y3.C1.L;P;U;a1.s1.a1.n;t1.h;v;²;×;O5;N1;E.l1;v.n2;c1.e.e1.i;o1;p.u1;i.P1.h2.i1.a;o2.b2;i.o.i;Q1.i1.n1.g1.x;Rz. ;&;,;.1;J./;1;4;6;A3. ;.;F1;T.B1;R.C;D;E3. ;S1.P;U;F;G;H1.S;I2.A;C1. ;J;K;L1;P.M5;1.2.3.5.6.N;O2.H;T2;A.O.P;Q;R1;F.S4;,...?.T.T;U4;B.M.N.S.V;X;c;f1;M1...h2.A;B;ò;S11. ;&;,;.4.E;M;O;T1..3.B;D;M;1;3;4;5;6;8;9;A3. ;8;S2;E.I.B;C3.A1. ;R2.A.U.T;D;E6. ;5;C3;A.O.R.I1.F.O;U;F3;&.H.O1.S.G1;D.H3.2;3;L;I2. ;S1.O.K2.I.Y.L3;A2. ;.;I1. ;O.M3;A1. ;I.U1.R.N5.A.C3.A.B.C.E.F.O.O5. ;A1.I;E;S1;U.V;P7;A7;A.C.D.M.N.R.S.E1. ;I4;C.D.N.R.L1;O.O.U.Y.Q1. ;R;S1;W.T9.A1. ;C;D;F;I;L;M;S;V;U7.B.L.M.N.P.R.S.V;W1.R;X1.M;h1.i1.g1.a1.o;p1.i1.o1;n.t2.B;i1.c1.i;T4.a2.i2.g1.a.s1.c;v1.e1.s;e1.a1.m1.p;u1.i2.l;r;à;Um..1.N1..1.C;/1.1;11. .21.1;L1.T;M1.N;N4.C1.L;D2. .P.K;R1. .a;b2;a.i.d;g1.l;i1.g.l2;i.y.m;no. ;a1.n.b;c;d;e1;s.f;g;h;i2.d;n;j;k;l;m;n;o;p;q;r;s;t;u;v;w;p;r3;a.e.u1.k;s3. ;h;t1;r.t4.h;n;r;t;x;z;í;W2.P1.:4.A1.F;I2.B;N1.H.O1.V;R1.F1.C2.N.U.i1.k1.i1.E1.l1.i;X7;a.e.h.i.o.u.y.Y3.e1.t1.h;p;s;[5.A;E;I;a;e;_2._1.i;e;`3.a;e;i;a7; .m1;a1;r1. .n1;d2; .ě.p1;r1;t.r1;t1;í.u1;s1;s1;i1. .v1;u1;t.d3.a1.s1. ;e2.m1. ;r1. ;i2.c1.h1. ;e1.s1.e2.m;r;e8;c1;o1;n1;o1;m1;i1;a.e1;w.l1;i1;t1;e1;i.m1;p1;e1;z.n1;t1;e1;n1;d.s2;a1. .t4;a1; .e1; .i1;m1;a1;r.r1;u1.t.u1.p1. ;w.f3. ;M;y1.i;h9. ;,;.;C;a1.u1.t1;b.e2.i1.r1;a.r1.m1.a1.n;o4.m2.a1; .m;n8; .b.d.e3; .d.y.g.i.k.v.r1.s1. ;u1.r;r1. ;t1;t1;p1;:.i6;b1;n.e1;r.n2;f2;l1;u1;ê.o1;a.s1;t1;a1;l1;a.r1; .s1; .u.k1.u1. ;l3.c1.d;s1. ;v1.a;ma. ;,;R;b1.a.e1.i1.n;f;p;t1.a.u1.l1.t1.i1.c1.a1.m1.p1.i;×;n6. ;V;W;d1; .t;×;o8;c2;h1;o.u1;p.d1;d1;y.f1; .g1;g1;i.no. ;';,;/;a;b;c1.o;d;e2.i;r;f;g;i;l;m;n;o;r;s;t;u;w;y;z;–;r1;i1;g1;e.t1;r1.s;u1;i.r3. ;&;f;s9.,;?;R;f2.e.o.i1.c1.h;l1. ;p2.3;i1. ;r1.g;v3.a.e.i.t2.A;S;uc; ...b2.e;l;f.k2.a;i;m1;a1. .n3;a3; .n5.a;c;n;s;t;r1;y.e2; .i.i8.c2.o1.r1.p;u1.m;d1;i1.o;g1.n;l1.l;m1;o.n;s1.s;v1.o1;c.r5;a.e.i.l.o.s3. ;h;u1.r2;e.p3;a.e.i.t2.m;t;v.w1.a;xb. ;';,;.;8;b;k;l;m1;a.t;y1. ;y1.l;{1.a;|1.a;£1.8;À;Á;Ä;Å;Æ;É;Ò;Ó;Ö;Ü;à;á;æ;è;é1;t3.a;o;u;í;ö;ü1; .Ā;ā;ī;İ;Ō;ō;œ;Ω;α;ε;ω;ϵ;е;–2.e;i;ℓ;";
function fill(node) {
var kidCount = parseInt(dict, 36) || 0,
offset = kidCount && kidCount.toString(36).length;
node.article = dict[offset] == "." ? "a" : "an";
dict = dict.substr(1 + offset);
for (var i = 0; i < kidCount; i++) {
var kid = node[dict[0]] = {}
dict = dict.substr(1);
fill(kid);
}
}
fill(root);
return {
raw: root,
//Usage example: AvsAnSimple.query("example")
//example returns: "an"
query: function (word) {
var node = root, sI = 0, result, c;
do {
c = word[sI++];
} while ('"‘’“”$\''.indexOf(c) >= 0);//also terminates on end-of-string "undefined".
while (1) {
result = node.article || result;
node = node[c];
if (!node) return result;
c = word[sI++] || " ";
}
}
};
})({})
Now, the problem is that I can't find a way to use this function in conjunction with the replaceExpr. The following obviously wouldn't work because of order precedence :
var a = replaceExpr("Theodor is " + AvsAnSimple(zxappearancexz) + "man. He seems rather " + AvsAnSimple(zxpersonalityxz).")
I just recently started learning javascript so my knowledge is rather limited. Any ideas how I could overcome this?
Thank you!
You could use a regular expression to optionally match the " a " or "an" before your word in the input string and store that matched portion in a variable using the String.match() function, then check if that " a " or " an " exists in your matched string, do the manipulations you need to do and store that manipulated string in a separate variable, then use String.replace() to find that previously matched string again, and replace it wit
your manipulated string. The regular expression you could use for this is /(\san?\s)?(zx\w*zx)/gm
See the regular expression here for more context.
Thank you Joseph! With your help I managed to find something that works by using your regular expression. Here's my function :
function replaceExpr(a) {
var nbExprToReplace = 1;
while (nbExprToReplace == 1) {
if (a.search("zx") == -1) {
nbExprToReplace = 0;
} else {
var currentGroup = a.match(/(\san?\s)?(zx\w*xz)/);
var exprToChange = currentGroup[2];
exprToChange = exprToChange.slice(2,-2);
var exprToChange = window[exprToChange];
if (typeof exprToChange !== "function") {
alert("the keyword is not a recognized function!");
break;
} else {
exprToChange = exprToChange();
var final = exprToChange
};
if (currentGroup[1] === undefined) {
} else {
var newArticle = AvsAnSimple.query(exprToChange);
final = newArticle.concat(" " + final)
};
a = a.replace(currentGroup[0], " " + final);
};
};
return a;
};

Run Angular Service after 1st Service has completed

I have two Angular services. The first creates a cookie and the second sets a URL to include the created cookie. I need the first to run before the second. Normally this is not an issue when using a service as my services tend to use a http call that has a success output, but in this instance, I don't have a success call and I don't know how I can trigger that the first is complete. Here is my code as it stands:
// Set Session Cookie
appCheckCookie.getCookie();
// Get Cart URL with Session Cookie
$scope.cartURL = appCartURL.getCartURL();
I have tried wrapping these in their own functions and trying to execute them in order, but that didn't work. and I tried something like this, but it didn't work:
appCheckCookie.getCookie(function(){
$scope.cartURL = appCartURL.getCartURL();
});
Here are my two services:
appServices.factory('appCheckCookie', ['$http', function ($http) {
var getCookie = function () {
// Create session cookie
function createCookie(name, value, days) {
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
var expires = "; expires=" + date.toGMTString();
}
else var expires = "";
document.cookie = name + "=" + value + expires + "; path=/";
}
// Function to read cookie
function readCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
}
var x = readCookie('ppkcookie')
if (x == null) {
// Get Session ID from backend URL
$http.post('/getSession', {}).success(function (data) {
// Create cookie from session ID from backend. Note that setting the days to 0 deletes the cookie on browser close (recommended)
createCookie('ppkcookie', data, 0);
console.log("Cookie Created: " + data);
}, function (err) {
console.log(err);
});
}
else {
var data = readCookie('ppkcookie');
console.log("Cookie Exists: " + data);
}
};
return {
getCookie: getCookie
};
}]),
appServices.factory('appCartURL', ['$http', function ($http) {
var getCartURL = function () {
// Function to read cookie
function readCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
}
var x = readCookie('ppkcookie')
var baseCartURL = 'http://myecommsite.net/basket.html?Session_ID='
var cartURL = baseCartURL + x;
return cartURL;
};
return {
getCartURL: getCartURL
};
}]),
In case of handling Async operations, Promise should be one of the APIs to consider.
What is Promise?
This may give you some inspirations of how to use it in your case:
// Namespace YourService
function readCookie(name) {
return new Promise(function(resolve, reject){
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0)
resolve(c.substring(nameEQ.length, c.length));
}
resolve(null); // consider reject(null)?
});
}
// Run in service
YourService.readCookie(name).then(function(cookieResponse) {
// Run your second service after cookie retrieved
}).catch(function(reason) {
// Catch reject
});
Another thing needs to be mentioned would be IE does not support Promise natively(Of course it doesn't..), so to use it in IE, you need to apply a polyfill.
Hope this can help.
Great advice on the use of $q. Along with a colleague, this is working since we only need the 1st service to signal that it is complete:
appServices.factory('appCheckCookie', ['$q', '$http', function ($q, $http) {
var getCookie = function () {
var deferred = $q.defer();
// Create session cookie
function createCookie(name, value, days) {
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
var expires = "; expires=" + date.toGMTString();
}
else var expires = "";
document.cookie = name + "=" + value + expires + "; path=/";
}
// Function to read cookie
function readCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
}
var x = readCookie('ppkcookie')
if (x == null) {
// Get GUID for the cookie
$http.post('/getSession', {}).success(function (data) {
// Create cookie. Note that setting the days to 0 deletes the cookie on browser close (recommended)
createCookie('ppkcookie', data, 0);
console.log("Cookie Created: " + data);
deferred.resolve(data);
}, function (err) {
console.log(err);
});
}
else {
var cookieExists = readCookie('ppkcookie');
console.log("Cookie Exists: " + cookieExists);
deferred.resolve(cookieExists);
}
return deferred.promise;
};
return {
getCookie: getCookie
};
}]),
Then in the controller:
appCheckCookie.getCookie().then(function (data) {
// Get Cart URL
$scope.cartURL = appCartURL.getCartURL();
console.log($scope.cartURL);
});

Do I need to use callbacks?

var yModule = require('youtube-node'),
nodeYoutube = new yModule();
nodeYoutube.setKey("key");
module.exports.getVideoLength = function (vData){
youTube.getById(vData, function (result) {
return convertTime(result['items'][0]['contentDetails']['duration']);
})
};
var convertTime = function (time){
var reptms = /(?:(\d+)DT)?(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/;
var days = "00", hours = "00", minutes = "00", seconds = "00", formattedTime;
//if (reptms.test(time)) {
var matches = reptms.exec(time);
console.log(matches);
if (matches[1]) days = String(matches[1]);
if (matches[2]) hours = String(matches[2]);
if (matches[3]) minutes = String(matches[3]);
if (matches[4]) seconds = String(matches[4]);
formattedTime = "[" + days + ":" + hours + ":" + minutes + ":" + seconds + "]";
return formattedTime;
//}
};
I'm struggling to understand callbacks even after reading a few things about it.
nodeJs callbacks simple example this helped a little, but I'm still unclear about how it works. I've spent the past hour trying to figure out how to write this using callbacks.
This module is being called by this:
ytRetrieve.getVideoLength(youtube_parser(text))
youtube_parser's function:
function youtube_parser(url){
var regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#\&\?]*).*/;
var match = url.match(regExp);
//console.log(match);
if (match&&match[7]){
return match[7].split(" ")[0];
}
}
You need to use callbacks. The issue with your code youtube_parser( is that you are calling the function. A callback is a function that is passed as an argument to be called later. If you call the function, a string is returned. getVideoLength is expecting a function as an argument, not a string.
Instead use getVideoLength(youtube_parser). This actually passes in the youtube_parser function itself to be called later (i.e. when getVideoLength completes). The arguments to youtube_parser may need to be (error, url) instead, though.
Here is a solution I came up with. Is there anything I can do to enhance this code?
Thank you for your help.
Main.js
var ytempRetrieve = require('./youtube'), ytRetrieve = new ytempRetrieve();
var ytRegex = /(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?/;
bot.addListener('message', function (from, to, text, message) {
if (text.match(ytRegex)) {
console.log(text);
youtube_parser(text, to, ytRetrieve.getVideoLength)
}
});
function youtube_parser(url, to, callback) {
var regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#\&\?]*).*/;
var match = url.match(regExp);
//console.log(match);
if (match && match[7]) {
callback(match[7].split(" ")[0], function (res) {
setTimeout(function () {
bot.say(to, match[7].split(" ")[0] + " is " + res + " long.")
}, 1500)
});
}
}
youtube.js
var yModule = require('youtube-node'),
nodeYoutube = new yModule(),
apiKey = require('./config');
var youtube = function () {
var self = this;
self.time = null;
self.setAPIKey = function (key) {
nodeYoutube.setKey(key);
};
apiKey.getAPIKey(self.setAPIKey);
self.getVideoLength = function (vData, callback) {
nodeYoutube.getById(vData, function (result) {
callback(self.convertTime(result['items'][0]['contentDetails']['duration']));
})
};
self.convertTime = function (time) {
var reptms = /(?:(\d+)DT)?(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/;
var days = 0, hours = 0, minutes = 0, seconds = 0, formattedTime;
//if (reptms.test(time)) {
var matches = reptms.exec(time);
console.log(matches);
if (matches[1]) days = Number(matches[1]);
if (matches[2]) hours = Number(matches[2]);
hours += days * 24;
if (hours.toString().length === 1) {
hours = "0" + hours
}
if (matches[3]) minutes = String(matches[3]);
if (minutes.toString().length === 1) {
minutes = "0" + minutes
}
if (matches[4]) seconds = String(matches[4]);
if (seconds.toString().length === 1) {
seconds = "0" + seconds
}
formattedTime = "[" + hours + ":" + minutes + ":" + seconds - 1 + "]";
return (formattedTime);
//}
};
};
module.exports = youtube;

Subclassing events.EventEmitter not working

How do I subclass EventEmitter? I am trying the following test:
var util = require('util')
,events = require('events');
function Downloader() {
events.EventEmitter.call(this);
}
util.inherits(Downloader, events.EventEmitter);
console.log(new Downloader() instanceof events.EventEmitter); // true
Downloader.prototype.download = function(episode) {
var self = this;
var statusMessage = 'Downloading: ' + episode;
self.emit('status', statusMessage);
setTimeout(function() {
var finishedMessage = 'Downloaded ' + episode;
self.emit('finished', finishedMessage);
}, 5000);
}
exports.Downloader = new Downloader();
Node complains 'object' has no method 'on'. However when check the Downloader instanceOf EventEmitter is returns true.
Can someone explain? Thanks!
Oh geez. First of all it should be 'module.exports'. Secondly its 'instanceof' not 'instanceOf'.
My bad. Here's a working example
var util = require('util')
,events = require('events');
function Downloader() {
events.EventEmitter.call(this);
}
util.inherits(Downloader, events.EventEmitter);
Downloader.prototype.download = function(episode) {
var self = this;
var statusMessage = 'Downloading: ' + episode;
self.emit('status', statusMessage);
setTimeout(function() {
var finishedMessage = 'Downloaded ' + episode;
self.emit('finished', finishedMessage);
}, 5000);
}
module.exports = new Downloader();

Categories