Socket
Socket
Sign inDemoInstall

lastfm

Package Overview
Dependencies
Maintainers
1
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

lastfm - npm Package Compare versions

Comparing version 0.7.0 to 0.8.0

8

History.md
# Changelog
## 0.8.0
### Breaking changes
* Removed old handler options which were deprecated in 0.6.0.
### New features
* Scrobble request which return error codes 11, 16 or 29 are automatically retried.
## 0.7.0

@@ -4,0 +12,0 @@

11

lib/lastfm/lastfm-base.js

@@ -21,2 +21,3 @@ var EventEmitter = require("events").EventEmitter

var defaultBlacklist = ["error", "success", "handlers"];
LastFmBase.prototype.filterParameters = function(parameters, blacklist) {

@@ -33,6 +34,14 @@ var filteredParams = {};

function isBlackListed(name) {
return _(blacklist).include(name);
return _(defaultBlacklist).include(name) || _(blacklist).include(name);
}
};
LastFmBase.prototype.scheduleCallback = function(callback, delay) {
return setTimeout(callback, delay);
};
LastFmBase.prototype.cancelCallback = function(identifier) {
clearTimeout(identifier);
};
module.exports = LastFmBase;

8

lib/lastfm/lastfm-info.js

@@ -12,8 +12,2 @@ var LastFmBase = require("./lastfm-base");

function registerEventHandlers(options) {
if (options.error) {
that.on("error", options.error);
}
if (options.success) {
that.on("success", options.success);
}
that.registerHandlers(options.handlers);

@@ -28,3 +22,3 @@ }

var params = that.filterParameters(options, ["error", "success", "handlers"])
var params = that.filterParameters(options)
, method = type + ".getinfo"

@@ -31,0 +25,0 @@ , request = lastfm.request(method, params);

@@ -31,6 +31,2 @@ if (global.GENTLY_HIJACK) require = GENTLY_HIJACK.hijack(require);

function registerEventHandlers(handlers) {
utils.registerHandlers(that, handlers);
}
function sendRequest(host, url, params) {

@@ -59,3 +55,3 @@ var httpVerb = isWriteRequest() ? "POST" : "GET"

function buildRequestParams(params) {
var requestParams = that.filterParameters(params, ["handlers", "signed", "write", "error", "success"]);
var requestParams = that.filterParameters(params, ["signed", "write"]);
requestParams.method = method;

@@ -62,0 +58,0 @@ requestParams.api_key = requestParams.api_key || lastfm.api_key;

@@ -30,9 +30,2 @@ var LastFmBase = require("./lastfm-base");

function registerEventHandlers(options) {
if (options.error) {
that.on("error", options.error);
}
if (options.authorised) {
that.on("authorised", options.authorised);
}
that.registerHandlers(options.handlers);

@@ -39,0 +32,0 @@ }

var _ = require("underscore")
, LastFmBase = require("./lastfm-base");
, LastFmBase = require("./lastfm-base")
, retryOnErrors = [
11, // Service offline
16, // Temporarily unavailable
29 // Rate limit exceeded
]
, retrySchedule = [
10 * 1000, // 10 seconds
30 * 1000, // 30 seconds
60 * 1000, // 1 minute
5 * 60 * 1000, // 5 minutes
15 * 60 * 1000, // 15 minutes
30 * 60 * 1000 // 30 minutes
];
var LastFmUpdate = function(lastfm, method, session, options) {
var that = this;
options = options || {};
options = options || { };
LastFmBase.call(this);

@@ -12,3 +25,6 @@

if (!session.isAuthorised()) {
this.emit("error", new Error("Session is not authorised"));
this.emit("error", {
error: 4,
message: "Authentication failed"
});
return;

@@ -19,12 +35,5 @@ }

}
update(method, options);
function registerEventHandlers(options) {
if (options.error) {
that.on("error", options.error);
}
if (options.success) {
that.on("success", options.success);
}
that.registerHandlers(options.handlers);

@@ -35,16 +44,44 @@ }

if (method == "scrobble" && !options.timestamp) {
that.emit("error", new Error("Timestamp is required for scrobbling"));
that.emit("error", {
error: 6,
message: "Invalid parameters - Timestamp is required for scrobbling"
});
return;
}
var params = buildRequestParams(options),
requestMethod = method == "scrobble" ? "track.scrobble" : "track.updateNowPlaying",
request = lastfm.request(requestMethod, params);
request.on("success", handleResponse);
request.on("error", bubbleError);
}
var retryCount = 0
, params = buildRequestParams(options)
, requestMethod = method == "scrobble" ? "track.scrobble" : "track.updateNowPlaying";
makeRequest();
function handleResponse(response) {
if (!response) return;
that.emit("success", options.track);
function makeRequest() {
var request = lastfm.request(requestMethod, params);
request.on("error", errorCallback);
request.on("success", successCallback);
}
function successCallback(response) {
if (response) {
that.emit("success", options.track);
}
}
function errorCallback(error) {
if (shouldBeRetried(error)) {
var delay = delayFor(retryCount++)
, retry = {
error: error.error,
message: error.message,
delay: delay
};
that.emit("retrying", retry);
that.scheduleCallback(makeRequest, delay);
return;
}
bubbleError(error);
}
function shouldBeRetried(error) {
return method == "scrobble" && _(retryOnErrors).include(error.error)
}
}

@@ -57,6 +94,11 @@

function buildRequestParams(params) {
var requestParams = that.filterParameters(params, ["error", "success", "handlers"]);
var requestParams = that.filterParameters(params);
requestParams.sk = session.key;
return requestParams;
}
function delayFor(retryCount) {
var index = Math.min(retryCount, retrySchedule.length - 1);
return retrySchedule[index];
}
}

@@ -67,2 +109,1 @@

module.exports = LastFmUpdate;

@@ -34,7 +34,2 @@ var LastFmBase = require("./lastfm-base");

that.registerHandlers(options.handlers);
var events = ["error", "lastPlayed", "nowPlaying", "stoppedPlaying", "scrobbled"];
events.forEach(function (event) {
if (options[event]) that.on(event, options[event]);
});
}

@@ -53,3 +48,5 @@

request.on("error", bubbleError);
if (isStreaming) timeout = setTimeout(check, rate * 1000);
if (isStreaming) {
timeout = that.scheduleCallback(check, rate * 1000);
}
}

@@ -111,3 +108,3 @@

function stop() {
clearTimeout(timeout);
that.cancelCallback(timeout);
isStreaming = false;

@@ -114,0 +111,0 @@ }

{
"name": "lastfm",
"description": "Read and write to Last.fm",
"version": "0.7.0",
"version": "0.8.0",
"author": "James Scott <jammus@gmail.com>",

@@ -6,0 +6,0 @@ "contributors": [

@@ -97,6 +97,2 @@ # lastfm-node

- *lastPlayed*, *nowPlaying*, *scrobbled*, *stoppedPlaying*, *error*
**Deprecated:** Event listeners.
Events:

@@ -181,2 +177,4 @@

If a scrobble request receives an 11 (service offline), 16 (temporarily unavailable) or 29 (rate limit exceeded) error code from Last.fm then the request is automatically retried until it is permanently rejected or accepted. The first retry attempt is made after 10 seconds with subsequent requests delayed by 30 seconds, 1 minute, 5 minutes, 15 minutes and then every 30 minutes.
Options:

@@ -198,10 +196,2 @@

- *success*
**Deprecated:** Listener for `success` event.
- *error*
**Deprecated:** Listener for `error` event.
Events:

@@ -213,2 +203,9 @@

- *retrying(retry)*
Scrobble request was not successful but will be retried after a delay. Retry object contains the following properties:
`delay` - The time in milliseconds before the request will be retried.
`error` - The error code returned by the Last.fm API.
`message` - The error message returned by the Last.fm API.
- *error(track, error)*

@@ -242,10 +239,2 @@

- *success*
**Deprecated:** Listener for `success` event.
- *error*
**Deprecated:** Listener for `error` event.
Special cases:

@@ -252,0 +241,0 @@

@@ -17,1 +17,2 @@ var path = require("path");

}
global.emptyFn = function() { };

@@ -61,3 +61,3 @@ require('./common');

it("ignores blacklist of parameters", function() {
it("filteres out blacklisted parameters", function() {
var copy = lastfmBase.filterParameters(original, ["one", "three"]);

@@ -68,2 +68,13 @@ assert.equal(typeof copy.one, "undefined");

});
it("automatically removed error, success, handler parameters", function() {
var copy = lastfmBase.filterParameters({
error: emptyFn,
success: emptyFn,
handlers: { }
});
assert.equal(typeof copy.error, "undefined");
assert.equal(typeof copy.success, "undefined");
assert.equal(typeof copy.handlers, "undefined");
});
})();

@@ -14,10 +14,2 @@ require("./common.js");

it("accepts listeners in options (deprecated)", function() {
var handlers = { error: function() {}, success: function() {} };
gently.expect(handlers, "error");
gently.expect(handlers, "success");
var info = new LastFmInfo(lastfm, "", handlers);
info.emit("success");
});
it("accepts listeners in handler options", function() {

@@ -24,0 +16,0 @@ var handlers = { error: function() {}, success: function() {} };

@@ -27,26 +27,2 @@ require("./common.js");

it("event handlers can be specified in options (deprecated)", function() {
var handlers = {};
gently.expect(handlers, "error");
gently.expect(handlers, "lastPlayed");
gently.expect(handlers, "nowPlaying");
gently.expect(handlers, "stoppedPlaying");
gently.expect(handlers, "scrobbled");
var trackStream = new RecentTracksStream(lastfm, "username", {
error: handlers.error,
lastPlayed: handlers.lastPlayed,
nowPlaying: handlers.nowPlaying,
stoppedPlaying: handlers.stoppedPlaying,
scrobbled: handlers.scrobbled
});
trackStream.emit("error");
trackStream.emit("lastPlayed");
trackStream.emit("nowPlaying");
trackStream.emit("stoppedPlaying");
trackStream.emit("scrobbled");
});
it("event handlers can be specified in options", function() {

@@ -313,1 +289,38 @@ var handlers = {};

})();
(function() {
var lastfm, gently;
describe("Streaming")
var tmpScheduleFn;
before(function() {
tmpScheduleFn = RecentTracksStream.prototype.scheduleCallback;
lastfm = new LastFmNode();
gently = new Gently();
});
after(function() {
RecentTracksStream.prototype.scheduleCallback = tmpScheduleFn;
});
it("queries API every 10 seconds", function() {
var trackStream = new RecentTracksStream(lastfm, "username");
var count = 0;
RecentTracksStream.prototype.scheduleCallback = function(callback, delay) {
count++;
if (count === 10) {
trackStream.stop();
}
assert.ok(delay, 10000);
gently.expect(lastfm, "request", function(method, params) {
return new fakes.LastFmRequest();
});
callback();
};
gently.expect(lastfm, "request", function(method, params) {
return new fakes.LastFmRequest();
});
trackStream.start();
});
})();

@@ -176,3 +176,3 @@ require("./common");

function whenReceiving(data) {
if (!data instanceof Array) {
if (data.constructor.name !== 'Array') {
data = [data];

@@ -179,0 +179,0 @@ }

@@ -150,12 +150,2 @@ require('./common.js');

it("can have error handler specified with authorise call (deprecated)", function() {
var handler = { error: function(error) { } };
gently.expect(handler, "error", function(error) {
assert.equal("No token supplied", error.message);
});
session.authorise("", {
error: handler.error
});
});
it("updates session key and user when successful", function() {

@@ -181,12 +171,2 @@ whenReadRequestReturns(FakeData.SuccessfulAuthorisation);

it("can have authorised handler specified with authorise call (deprecated)", function() {
var handler = { authorised: function(session) { } };
whenReadRequestReturns(FakeData.SuccessfulAuthorisation);
gently.expect(handler, "authorised");
session.authorise("token", {
authorised: handler.authorised
});
request.emit("success", returndata);
});
it("bubbles up errors", function() {

@@ -193,0 +173,0 @@ var errorMessage = "Bubbled error";

@@ -8,13 +8,2 @@ require('./common.js');

describe("new LastFmUpdate")
it("can have success and error handlers specified at creation (deprecated)", function() {
var gently = new Gently();
var lastfm = new LastFmNode();
var update = new LastFmUpdate(lastfm, "method", new LastFmSession(lastfm, "user", "key"), {
error: gently.expect(function error() {}),
success: gently.expect(function success() {})
});
update.emit("error");
update.emit("success");
});
it("can have success and error handlers specified in option at creation", function() {

@@ -33,3 +22,3 @@ var gently = new Gently();

(function() {
var request, returndata, options, session, method, gently, lastfm, authorisedSession, requestError;
var request, returndata, options, session, method, gently, lastfm, authorisedSession, errorCode, errorMessage, update;

@@ -45,8 +34,13 @@ function setupFixture() {

authorisedSession = new LastFmSession(lastfm, "user", "key");
requestError = null;
errorCode = -1;
errorMessage = null;
update = undefined;
}
function whenWriteRequestReturns(data) {
function whenRequestReturns(data) {
errorCode = -1;
errorMessage = null;
returndata = JSON.parse(data);
gently.expect(lastfm, "request", function(method, params) {
request = new fakes.LastFmRequest();
gently.expect(lastfm, "request", function() {
return request;

@@ -56,5 +50,7 @@ });

function whenWriteRequestThrowsError(errorMessage) {
requestError = errorMessage;
gently.expect(lastfm, "request", function(method, params) {
function whenRequestThrowsError(code, message) {
errorCode = code;
errorMessage = message;
request = new fakes.LastFmRequest();
gently.expect(lastfm, "request", function() {
return request;

@@ -77,4 +73,3 @@ });

function expectSuccess(assertions) {
options.handlers = options.handlers || {};
options.handlers.success = function(track) {
var checkSuccess = function(track) {
if (assertions) {

@@ -84,16 +79,58 @@ assertions(track);

};
new LastFmUpdate(lastfm, method, session, options);
request.emit("success", returndata);
if (update) {
update.on("success", checkSuccess);
}
else {
options.handlers = options.handlers || {};
options.handlers.success = checkSuccess;
}
doUpdate();
}
function expectError(expectedError) {
function expectError(errorCode, expectedError) {
var checkError = function(error) {
if (errorCode || expectedError) {
assert.equal(expectedError, error.message);
assert.equal(errorCode, error.error);
}
};
if (update) {
update.on("error", checkError);
}
else {
options.handlers = options.handlers || {};
options.handlers.error = gently.expect(checkError);
}
doUpdate();
}
function doNotExpectError() {
options.handlers = options.handlers || {};
options.handlers.error = gently.expect(function(error) {
assert.equal(expectedError, error.message);
});
new LastFmUpdate(lastfm, method, session, options);
if (requestError) {
request.emit("error", new Error(requestError));
options.handlers.error = function checkNoErrorThrown(error) {
assert.ok(false);
};
doUpdate();
}
function expectRetry(callback) {
callback = callback || function() { };
if (update) {
gently.expect(update, "emit", function(event, retry) {
assert.equal(event, "retrying");
callback(retry);
});
}
else {
options.handlers = options.handlers || { };
options.handlers.retrying = gently.expect(callback);
}
doUpdate();
}
function doUpdate() {
update = update || new LastFmUpdate(lastfm, method, session, options);
if (errorMessage) {
request.emit("error", { error: errorCode, message: errorMessage });
}
else {
request.emit("success", returndata);

@@ -109,6 +146,11 @@ }

it("fail when the session is not authorised", function() {
var session = new LastFmSession();
assert.throws(function() {
new LastFmUpdate(lastfm, "method", session);
});
var session = new LastFmSession()
, update = new LastFmUpdate(lastfm, "method", session, {
handlers: {
error: gently.expect(function(error) {
assert.equal(error.error, 4);
assert.equal(error.message, "Authentication failed");
})
}
});
});

@@ -143,3 +185,3 @@

it("emits success when updated", function() {
whenWriteRequestReturns(FakeData.UpdateNowPlayingSuccess);
whenRequestReturns(FakeData.UpdateNowPlayingSuccess);
andMethodIs("nowplaying");

@@ -181,3 +223,3 @@ andSessionIs(authorisedSession);

var errorMessage = "Bubbled error";
whenWriteRequestThrowsError(errorMessage);
whenRequestThrowsError(100, errorMessage);
andMethodIs("nowplaying");

@@ -189,3 +231,3 @@ andSessionIs(authorisedSession);

});
expectError(errorMessage);
expectError(100, errorMessage);
});

@@ -203,3 +245,4 @@

error: gently.expect(function error(error) {
assert.equal("Timestamp is required for scrobbling", error.message);
assert.equal(6, error.error);
assert.equal("Invalid parameters - Timestamp is required for scrobbling", error.message);
})

@@ -236,3 +279,3 @@ }

it("emits success when updated", function() {
whenWriteRequestReturns(FakeData.ScrobbleSuccess);
whenRequestReturns(FakeData.ScrobbleSuccess);
andMethodIs("scrobble");

@@ -251,3 +294,3 @@ andSessionIs(authorisedSession);

var errorMessage = "Bubbled error";
whenWriteRequestThrowsError(errorMessage);
whenRequestThrowsError(100, errorMessage);
andMethodIs("scrobble");

@@ -259,3 +302,3 @@ andSessionIs(authorisedSession);

});
expectError(errorMessage);
expectError(100, errorMessage);
});

@@ -306,2 +349,152 @@

});
var tmpFn;
describe("update retries")
before(function() {
tmpFn = LastFmUpdate.prototype.scheduleCallback;
LastFmUpdate.prototype.scheduleCallback = function(callback, delay) { };
setupFixture();
andMethodIs("scrobble");
andSessionIs(authorisedSession);
andOptionsAre({
track: FakeTracks.RunToYourGrave,
timestamp: 12345678
});
});
after(function() {
LastFmUpdate.prototype.scheduleCallback = tmpFn;
});
it("a error which should trigger a retry does not bubble errors", function() {
whenRequestThrowsError(11, "Service Offline");
doNotExpectError();
});
it("service offline triggers a retry", function() {
whenRequestThrowsError(11, "Service Offline");
expectRetry();
});
it("rate limit exceeded triggers a retry", function() {
whenRequestThrowsError(29, "Rate limit exceeded");
expectRetry();
});
it("temporarily unavailable triggers a retry", function() {
whenRequestThrowsError(16, "Temporarily unavailable");
expectRetry();
});
it("nowplaying update never trigger retries", function() {
whenRequestThrowsError(16, "Temporarily unavailable");
andMethodIs("nowplaying");
expectError();
});
it("first retry schedules a request after a 10 second delay", function() {
whenRequestThrowsError(16, "Temporarily unavailable");
LastFmUpdate.prototype.scheduleCallback = gently.expect(function testSchedule(callback, delay) {
assert.equal(delay, 10000);
});
doUpdate();
});
function onNextRequests(callback, count) {
count = count || 1;
var gently = new Gently();
LastFmUpdate.prototype.scheduleCallback = gently.expect(count, callback);
doUpdate();
}
function lastRequest() {
LastFmUpdate.prototype.scheduleCallback = function() { };
}
function whenNextRequestThrowsError(request, code, message) {
whenRequestThrowsError(code, message);
request();
}
function whenNextRequestReturns(request, data) {
whenRequestReturns(data);
request();
}
it("retry triggers another request", function() {
whenRequestThrowsError(16, "Temporarily unavailable");
onNextRequests(function(nextRequest) {
lastRequest();
whenNextRequestThrowsError(nextRequest, 16, "Temporarily unavailable");
expectRetry();
});
});
it("emits succes if retry is successful", function() {
whenRequestThrowsError(16, "Temporarily unavailable");
onNextRequests(function(nextRequest) {
whenNextRequestReturns(nextRequest, FakeData.ScrobbleSuccess);
expectSuccess(function(track) {
assert.equal("Run To Your Grave", track.name);
});
});
});
it("emits succes if retry is non-retry error", function() {
whenRequestThrowsError(16, "Temporarily unavailable");
onNextRequests(function(nextRequest) {
whenNextRequestThrowsError(nextRequest, 6, "Invalid parameter");
expectError(6, "Invalid parameter");
});
});
it("retrying events include error received and delay details", function() {
whenRequestThrowsError(16, "Temporarily unavailable");
expectRetry(function(retry) {
assert.equal(retry.delay, 10000);
assert.equal(retry.error, 16);
assert.equal(retry.message, "Temporarily unavailable");
});
});
var retrySchedule = [
10 * 1000,
30 * 1000,
60 * 1000,
5 * 60 * 1000,
15 * 60 * 1000,
30 * 60 * 1000,
30 * 60 * 1000,
30 * 60 * 1000
];
it("follows a retry schedule on subsequent failures", function() {
var count = 0;
whenRequestThrowsError(16, "Temporarily unavailable");
onNextRequests(function(nextRequest, delay) {
var expectedDelay = retrySchedule[count++];
assert.equal(delay, expectedDelay);
if (count >= retrySchedule.length) {
lastRequest();
}
whenNextRequestThrowsError(nextRequest, 16, "Temporarily unavailable");
expectRetry();
}, retrySchedule.length);
});
it("includes delay in subsequent retry events", function() {
var count = 0;
whenRequestThrowsError(16, "Temporarily unavailable");
onNextRequests(function(nextRequest, delay) {
count++;
if (count >= retrySchedule.length) {
lastRequest();
}
var expectedDelay = retrySchedule[Math.min(count, retrySchedule.length - 1)];
whenNextRequestThrowsError(nextRequest, 16, "Temporarily unavailable");
expectRetry(function(retry) {
assert.equal(retry.delay, expectedDelay);
});
}, retrySchedule.length);
});
})();
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc