plug-login
Version:
Logs in to plug.dj.
374 lines (311 loc) • 8.89 kB
JavaScript
import fetch from 'node-fetch';
/*!
* cookie
* Copyright(c) 2012-2014 Roman Shtylman
* Copyright(c) 2015 Douglas Christopher Wilson
* MIT Licensed
*/
/**
* Module exports.
* @public
*/
var parse_1 = parse;
var serialize_1 = serialize;
/**
* Module variables.
* @private
*/
var decode = decodeURIComponent;
var encode = encodeURIComponent;
var pairSplitRegExp = /; */;
/**
* RegExp to match field-content in RFC 7230 sec 3.2
*
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
* field-vchar = VCHAR / obs-text
* obs-text = %x80-FF
*/
var fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;
/**
* Parse a cookie header.
*
* Parse the given cookie header string into an object
* The object has the various cookies as keys(names) => values
*
* @param {string} str
* @param {object} [options]
* @return {object}
* @public
*/
function parse(str, options) {
if (typeof str !== 'string') {
throw new TypeError('argument str must be a string');
}
var obj = {};
var opt = options || {};
var pairs = str.split(pairSplitRegExp);
var dec = opt.decode || decode;
for (var i = 0; i < pairs.length; i++) {
var pair = pairs[i];
var eq_idx = pair.indexOf('=');
// skip things that don't look like key=value
if (eq_idx < 0) {
continue;
}
var key = pair.substr(0, eq_idx).trim();
var val = pair.substr(++eq_idx, pair.length).trim();
// quoted values
if ('"' == val[0]) {
val = val.slice(1, -1);
}
// only assign once
if (undefined == obj[key]) {
obj[key] = tryDecode(val, dec);
}
}
return obj;
}
/**
* Serialize data into a cookie header.
*
* Serialize the a name value pair into a cookie string suitable for
* http headers. An optional options object specified cookie parameters.
*
* serialize('foo', 'bar', { httpOnly: true })
* => "foo=bar; httpOnly"
*
* @param {string} name
* @param {string} val
* @param {object} [options]
* @return {string}
* @public
*/
function serialize(name, val, options) {
var opt = options || {};
var enc = opt.encode || encode;
if (typeof enc !== 'function') {
throw new TypeError('option encode is invalid');
}
if (!fieldContentRegExp.test(name)) {
throw new TypeError('argument name is invalid');
}
var value = enc(val);
if (value && !fieldContentRegExp.test(value)) {
throw new TypeError('argument val is invalid');
}
var str = name + '=' + value;
if (null != opt.maxAge) {
var maxAge = opt.maxAge - 0;
if (isNaN(maxAge)) throw new Error('maxAge should be a Number');
str += '; Max-Age=' + Math.floor(maxAge);
}
if (opt.domain) {
if (!fieldContentRegExp.test(opt.domain)) {
throw new TypeError('option domain is invalid');
}
str += '; Domain=' + opt.domain;
}
if (opt.path) {
if (!fieldContentRegExp.test(opt.path)) {
throw new TypeError('option path is invalid');
}
str += '; Path=' + opt.path;
}
if (opt.expires) {
if (typeof opt.expires.toUTCString !== 'function') {
throw new TypeError('option expires is invalid');
}
str += '; Expires=' + opt.expires.toUTCString();
}
if (opt.httpOnly) {
str += '; HttpOnly';
}
if (opt.secure) {
str += '; Secure';
}
if (opt.sameSite) {
var sameSite = typeof opt.sameSite === 'string'
? opt.sameSite.toLowerCase() : opt.sameSite;
switch (sameSite) {
case true:
str += '; SameSite=Strict';
break;
case 'lax':
str += '; SameSite=Lax';
break;
case 'strict':
str += '; SameSite=Strict';
break;
default:
throw new TypeError('option sameSite is invalid');
}
}
return str;
}
/**
* Try decoding a string using a decoding function.
*
* @param {string} str
* @param {function} decode
* @private
*/
function tryDecode(str, decode) {
try {
return decode(str);
} catch (e) {
return str;
}
}
var promiseProps = function(obj) {
var awaitables = [];
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var a$ = obj[key];
awaitables.push(a$);
}
return Promise.all(awaitables).then(function (results) {
var byName = {};
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
byName[key] = results[i];
}
return byName;
});
};
var DEFAULT_HOST = 'https://plug.dj';
// Enhance a `fetch` options object to use a JSON body when sending data.
function json (opts) {
return Object.assign({}, opts,
{headers: Object.assign({}, opts.headers,
{'content-type': 'application/json'}),
body: JSON.stringify(opts.body)})
}
// Create an HTTP response error.
function error (response, status, message) {
var e = new Error(`${status}: ${message}`);
e.response = response;
e.status = status;
return e
}
// Get the JSON response from the plug.dj API, throwing if it is an error response.
function getJSON (response) {
return response.json().then((body) => {
if (body.status !== 'ok') {
throw error(response, body.status, body.data[0])
}
return body
})
}
// Extract the session cookie value from an array of set-cookie headers.
function getSessionCookie (headers) {
if (!headers) { return }
var cookie$$1 = parse_1(headers);
if (cookie$$1.session) { return cookie$$1.session }
}
// Build a "Cookie:" header value with a session cookie.
function makeSessionCookieHeader (session) {
return serialize_1('session', session, {
// Should not URL-encode: plug.dj only accepts unencoded "|" characters.
encode: value => value
})
}
function addCookieToHeaders (opts, session) {
if (!opts.headers || !opts.headers.cookie) {
opts.headers = Object.assign(opts.headers || {}, {
cookie: makeSessionCookieHeader(session)
});
}
return opts
}
// Get a CSRF token and session cookie for logging into plug.dj from their main
// page. Without the CSRF token, login requests will be rejected.
function getCsrf (opts) {
// for testing
if (opts._simulateMaintenance) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('Could not find CSRF token'));
}, 300);
})
}
return fetch(`${opts.host}/_/mobile/init`, json(opts))
.then((response) => promiseProps({
csrf: getJSON(response).then((body) => body.data[0].c),
session: getSessionCookie(response.headers.get('set-cookie'))
}))
}
// Log in to plug.dj with an email address and password.
// `opts` must contain headers with a session cookie.
function doLogin (opts, csrf, email, password) {
return fetch(`${opts.host}/_/auth/login`, json(Object.assign({}, opts,
{method: 'post',
body: { csrf, email, password }}))).then((response) => promiseProps({
session: getSessionCookie(response.headers.get('set-cookie')),
body: getJSON(response)
}))
}
function getAuthToken (opts) {
opts = normalizeOptions(opts);
return fetch(`${opts.host}/_/auth/token`, json(opts))
.then((response) => getJSON(response))
.then((body) => body.data[0])
}
function normalizeOptions (maybeOpts) {
if ( maybeOpts === void 0 ) maybeOpts = {};
var opts = Object.assign({}, maybeOpts);
if (!opts.host) {
opts.host = DEFAULT_HOST;
} else {
// Trim slashes
opts.host = opts.host.replace(/\/+$/, '');
}
return opts
}
function guest (opts) {
opts = normalizeOptions(opts);
return fetch(`${opts.host}/plug-socket-test`, opts).then((response) => {
if (!response.ok) {
throw error(response, response.status, response.statusText)
}
return response.text().then((body) => {
if (/<title>maintenance mode/.test(body)) {
throw error(response, 'maintenanceMode', 'The site is in maintenance mode')
}
var session = getSessionCookie(response.headers.get('set-cookie'));
opts = addCookieToHeaders(opts, session);
return promiseProps({
session,
cookie: makeSessionCookieHeader(session),
token: opts.authToken ? getAuthToken(opts) : null
})
})
})
}
function user (email, password, opts) {
opts = normalizeOptions(opts);
return getCsrf(opts)
.then((ref) => {
var csrf = ref.csrf;
var session = ref.session;
opts = addCookieToHeaders(opts, session);
return doLogin(opts, csrf, email, password)
})
.then((result) => promiseProps({
session: result.session,
cookie: makeSessionCookieHeader(result.session),
token: opts.authToken ? getAuthToken(opts) : null
}))
}
function login (email, password, opts) {
if (typeof email === 'string') {
return user(email, password, opts)
} else {
return guest(email)
}
}
login.user = user;
login.guest = guest;
login.getAuthToken = getAuthToken;
export default login;
//# sourceMappingURL=index.es.js.map