cozy-client-js
Version:
Javascript library to interact with a cozy
1,542 lines (1,292 loc) • 130 kB
JavaScript
(function(e, a) { for(var i in a) e[i] = a[i]; }(exports, /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 9);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.FetchError = undefined;
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); /* global fetch */
exports.cozyFetch = cozyFetch;
exports.cozyFetchJSON = cozyFetchJSON;
exports.cozyFetchRawJSON = cozyFetchRawJSON;
exports.handleInvalidTokenError = handleInvalidTokenError;
var _auth_v = __webpack_require__(3);
var _utils = __webpack_require__(1);
var _jsonapi = __webpack_require__(7);
var _jsonapi2 = _interopRequireDefault(_jsonapi);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function cozyFetch(cozy, path) {
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
return cozy.fullpath(path).then(function (fullpath) {
var resp = void 0;
if (options.disableAuth) {
resp = fetch(fullpath, options);
} else if (options.manualAuthCredentials) {
resp = cozyFetchWithAuth(cozy, fullpath, options, options.manualAuthCredentials);
} else {
resp = cozy.authorize().then(function (credentials) {
return cozyFetchWithAuth(cozy, fullpath, options, credentials);
});
}
return resp.then(function (res) {
return handleResponse(res, cozy._invalidTokenErrorHandler);
});
});
}
function cozyFetchWithAuth(cozy, fullpath, options, credentials) {
if (credentials) {
options.headers = options.headers || {};
options.headers['Authorization'] = credentials.token.toAuthHeader();
}
// the option credentials:include tells fetch to include the cookies in the
// request even for cross-origin requests
options.credentials = 'include';
return Promise.all([cozy.isV2(), fetch(fullpath, options)]).then(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2),
isV2 = _ref2[0],
res = _ref2[1];
if (res.status !== 400 && res.status !== 401 || isV2 || !credentials || options.dontRetry) {
return res;
}
// we try to refresh the token only for OAuth, ie, the client defined
// and the token is an instance of AccessToken.
var client = credentials.client,
token = credentials.token;
if (!client || !(token instanceof _auth_v.AccessToken)) {
return res;
}
options.dontRetry = true;
return (0, _utils.retry)(function () {
return (0, _auth_v.refreshToken)(cozy, client, token);
}, 3)().then(function (newToken) {
return cozy.saveCredentials(client, newToken);
}).then(function (credentials) {
return cozyFetchWithAuth(cozy, fullpath, options, credentials);
});
});
}
function cozyFetchJSON(cozy, method, path, body) {
var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
var processJSONAPI = typeof options.processJSONAPI === 'undefined' || options.processJSONAPI;
return fetchJSON(cozy, method, path, body, options).then(function (response) {
return handleJSONResponse(response, processJSONAPI);
});
}
function cozyFetchRawJSON(cozy, method, path, body) {
var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
return fetchJSON(cozy, method, path, body, options).then(function (response) {
return handleJSONResponse(response, false);
});
}
function fetchJSON(cozy, method, path, body) {
var options = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
options.method = method;
var headers = options.headers = options.headers || {};
headers['Accept'] = 'application/json';
if (method !== 'GET' && method !== 'HEAD' && body !== undefined) {
if (headers['Content-Type']) {
options.body = body;
} else {
headers['Content-Type'] = 'application/json';
options.body = JSON.stringify(body);
}
}
return cozyFetch(cozy, path, options);
}
function handleResponse(res, invalidTokenErrorHandler) {
if (res.ok) {
return res;
}
var data = void 0;
var contentType = res.headers.get('content-type');
if (contentType && contentType.indexOf('json') >= 0) {
data = res.json();
} else {
data = res.text();
}
return data.then(function (err) {
var error = new FetchError(res, err);
if (FetchError.isInvalidToken(error) && invalidTokenErrorHandler) {
invalidTokenErrorHandler(error);
}
throw error;
});
}
function handleJSONResponse(res) {
var processJSONAPI = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
var contentType = res.headers.get('content-type');
if (!contentType || contentType.indexOf('json') < 0) {
return res.text(function (data) {
throw new FetchError(res, new Error('Response is not JSON: ' + data));
});
}
var json = res.json();
if (contentType.indexOf('application/vnd.api+json') === 0 && processJSONAPI) {
return json.then(_jsonapi2.default);
} else {
return json;
}
}
function handleInvalidTokenError(error) {
try {
var currentOrigin = window.location.origin;
var requestUrl = error.url;
if (requestUrl.indexOf(currentOrigin.replace(/^(https?:\/\/\w+)-\w+\./, '$1.')) === 0) {
var redirectURL = currentOrigin + '?' + (0, _utils.encodeQuery)({ disconnect: 1 });
window.location = redirectURL;
}
} catch (e) {
console.warn('Unable to handle invalid token error', e, error);
}
}
var FetchError = exports.FetchError = function (_Error) {
_inherits(FetchError, _Error);
function FetchError(res, reason) {
_classCallCheck(this, FetchError);
var _this = _possibleConstructorReturn(this, (FetchError.__proto__ || Object.getPrototypeOf(FetchError)).call(this));
if (Error.captureStackTrace) {
Error.captureStackTrace(_this, _this.constructor);
}
// XXX We have to hardcode this because babel doesn't play nice when extending Error
_this.name = 'FetchError';
_this.response = res;
_this.url = res.url;
_this.status = res.status;
_this.reason = reason;
Object.defineProperty(_this, 'message', {
value: reason.message || (typeof reason === 'string' ? reason : JSON.stringify(reason))
});
return _this;
}
return FetchError;
}(Error);
FetchError.isUnauthorized = function (err) {
// XXX We can't use err instanceof FetchError because of the caveats of babel
return err.name === 'FetchError' && err.status === 401;
};
FetchError.isNotFound = function (err) {
// XXX We can't use err instanceof FetchError because of the caveats of babel
return err.name === 'FetchError' && err.status === 404;
};
FetchError.isInvalidToken = function (err) {
// XXX We can't use err instanceof FetchError because of the caveats of babel
return err.name === 'FetchError' && (err.status === 400 || err.status === 401) && err.reason && (err.reason.error === 'Invalid JWT token' || err.reason.error === 'Expired token');
};
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.unpromiser = unpromiser;
exports.isPromise = isPromise;
exports.isOnline = isOnline;
exports.isOffline = isOffline;
exports.sleep = sleep;
exports.retry = retry;
exports.getFuzzedDelay = getFuzzedDelay;
exports.getBackedoffDelay = getBackedoffDelay;
exports.createPath = createPath;
exports.encodeQuery = encodeQuery;
exports.decodeQuery = decodeQuery;
exports.warn = warn;
/* global navigator */
var FuzzFactor = 0.3;
function unpromiser(fn) {
return function () {
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
var value = fn.apply(this, args);
if (!isPromise(value)) {
return value;
}
var l = args.length;
if (l === 0 || typeof args[l - 1] !== 'function') {
return;
}
var cb = args[l - 1];
value.then(function (res) {
return cb(null, res);
}, function (err) {
return cb(err, null);
});
};
}
function isPromise(value) {
return !!value && typeof value.then === 'function';
}
function isOnline() {
return typeof navigator !== 'undefined' ? navigator.onLine : true;
}
function isOffline() {
return !isOnline();
}
function sleep(time, args) {
return new Promise(function (resolve) {
setTimeout(resolve, time, args);
});
}
function retry(fn, count) {
var delay = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 300;
return function doTry() {
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return fn.apply(undefined, args).catch(function (err) {
if (--count < 0) {
throw err;
}
return sleep(getBackedoffDelay(delay, count)).then(function () {
return doTry.apply(undefined, args);
});
});
};
}
function getFuzzedDelay(retryDelay) {
var fuzzingFactor = (Math.random() * 2 - 1) * FuzzFactor;
return retryDelay * (1.0 + fuzzingFactor);
}
function getBackedoffDelay(retryDelay) {
var retryCount = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
return getFuzzedDelay(retryDelay * Math.pow(2, retryCount - 1));
}
function createPath(cozy, isV2, doctype) {
var id = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '';
var query = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : null;
var route = '/data/';
if (!isV2) {
route += encodeURIComponent(doctype) + '/';
}
if (id !== '') {
route += encodeURIComponent(id);
}
var q = encodeQuery(query);
if (q !== '') {
route += '?' + q;
}
return route;
}
function encodeQuery(query) {
if (!query) {
return '';
}
var q = '';
for (var qname in query) {
if (q !== '') {
q += '&';
}
q += encodeURIComponent(qname) + '=' + encodeURIComponent(query[qname]);
}
return q;
}
function decodeQuery(url) {
var queryIndex = url.indexOf('?');
if (queryIndex < 0) {
queryIndex = url.length;
}
var queries = {};
var fragIndex = url.indexOf('#');
if (fragIndex < 0) {
fragIndex = url.length;
}
if (fragIndex < queryIndex) {
return queries;
}
var queryStr = url.slice(queryIndex + 1, fragIndex);
if (queryStr === '') {
return queries;
}
var parts = queryStr.split('&');
for (var i = 0; i < parts.length; i++) {
var pair = parts[i].split('=');
if (pair.length === 0 || pair[0] === '') {
continue;
}
var qname = decodeURIComponent(pair[0]);
if (queries.hasOwnProperty(qname)) {
continue;
}
if (pair.length === 1) {
queries[qname] = true;
} else if (pair.length === 2) {
queries[qname] = decodeURIComponent(pair[1]);
} else {
throw new Error('Malformed URL');
}
}
return queries;
}
var warned = [];
function warn(text) {
if (warned.indexOf(text) === -1) {
warned.push(text);
console.warn('cozy-client-js', text);
}
}
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.DOCTYPE_FILES = undefined;
exports.normalizeDoctype = normalizeDoctype;
var _utils = __webpack_require__(1);
var DOCTYPE_FILES = exports.DOCTYPE_FILES = 'io.cozy.files';
var KNOWN_DOCTYPES = {
files: DOCTYPE_FILES,
folder: DOCTYPE_FILES,
contact: 'io.cozy.contacts',
event: 'io.cozy.events',
track: 'io.cozy.labs.music.track',
playlist: 'io.cozy.labs.music.playlist'
};
var REVERSE_KNOWN = {};
Object.keys(KNOWN_DOCTYPES).forEach(function (k) {
REVERSE_KNOWN[KNOWN_DOCTYPES[k]] = k;
});
function normalizeDoctype(cozy, isV2, doctype) {
var isQualified = doctype.indexOf('.') !== -1;
if (isV2 && isQualified) {
var known = REVERSE_KNOWN[doctype];
if (known) return known;
return doctype.replace(/\./g, '-');
}
if (!isV2 && !isQualified) {
var _known = KNOWN_DOCTYPES[doctype];
if (_known) {
(0, _utils.warn)('you are using a non-qualified doctype ' + doctype + ' assumed to be ' + _known);
return _known;
}
throw new Error('Doctype ' + doctype + ' should be qualified.');
}
return doctype;
}
/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* WEBPACK VAR INJECTION */(function(btoa) {
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.AppToken = exports.AccessToken = exports.Client = exports.StateKey = exports.CredsKey = undefined;
var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* global btoa */
exports.client = client;
exports.registerClient = registerClient;
exports.updateClient = updateClient;
exports.unregisterClient = unregisterClient;
exports.getClient = getClient;
exports.getAuthCodeURL = getAuthCodeURL;
exports.getAccessToken = getAccessToken;
exports.refreshToken = refreshToken;
exports.oauthFlow = oauthFlow;
var _utils = __webpack_require__(1);
var _fetch = __webpack_require__(0);
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var StateSize = 16;
var CredsKey = exports.CredsKey = 'creds';
var StateKey = exports.StateKey = 'state';
var Client = exports.Client = function () {
function Client(opts) {
_classCallCheck(this, Client);
this.clientID = opts.clientID || opts.client_id || '';
this.clientSecret = opts.clientSecret || opts.client_secret || '';
this.registrationAccessToken = opts.registrationAccessToken || opts.registration_access_token || '';
if (opts.redirect_uris) {
this.redirectURI = opts.redirect_uris[0] || '';
} else {
this.redirectURI = opts.redirectURI || '';
}
this.softwareID = opts.softwareID || opts.software_id || '';
this.softwareVersion = opts.softwareVersion || opts.software_version || '';
this.clientName = opts.clientName || opts.client_name || '';
this.clientKind = opts.clientKind || opts.client_kind || '';
this.clientURI = opts.clientURI || opts.client_uri || '';
this.logoURI = opts.logoURI || opts.logo_uri || '';
this.policyURI = opts.policyURI || opts.policy_uri || '';
this.notificationPlatform = opts.notificationPlatform || opts.notification_platform || '';
this.notificationDeviceToken = opts.notificationDeviceToken || opts.notification_device_token || '';
if (!this.registrationAccessToken) {
if (this.redirectURI === '') {
throw new Error('Missing redirectURI field');
}
if (this.softwareID === '') {
throw new Error('Missing softwareID field');
}
if (this.clientName === '') {
throw new Error('Missing clientName field');
}
}
}
_createClass(Client, [{
key: 'isRegistered',
value: function isRegistered() {
return this.clientID !== '';
}
}, {
key: 'toRegisterJSON',
value: function toRegisterJSON() {
return {
redirect_uris: [this.redirectURI],
software_id: this.softwareID,
software_version: this.softwareVersion,
client_name: this.clientName,
client_kind: this.clientKind,
client_uri: this.clientURI,
logo_uri: this.logoURI,
policy_uri: this.policyURI,
notification_platform: this.notificationPlatform,
notification_device_token: this.notificationDeviceToken
};
}
}, {
key: 'toAuthHeader',
value: function toAuthHeader() {
return 'Bearer ' + this.registrationAccessToken;
}
}]);
return Client;
}();
var AccessToken = exports.AccessToken = function () {
function AccessToken(opts) {
_classCallCheck(this, AccessToken);
this.tokenType = opts.tokenType || opts.token_type;
this.accessToken = opts.accessToken || opts.access_token;
this.refreshToken = opts.refreshToken || opts.refresh_token;
this.scope = opts.scope;
}
_createClass(AccessToken, [{
key: 'toAuthHeader',
value: function toAuthHeader() {
return 'Bearer ' + this.accessToken;
}
}, {
key: 'toBasicAuth',
value: function toBasicAuth() {
return 'user:' + this.accessToken + '@';
}
}]);
return AccessToken;
}();
var AppToken = exports.AppToken = function () {
function AppToken(opts) {
_classCallCheck(this, AppToken);
this.token = opts.token || '';
}
_createClass(AppToken, [{
key: 'toAuthHeader',
value: function toAuthHeader() {
return 'Bearer ' + this.token;
}
}, {
key: 'toBasicAuth',
value: function toBasicAuth() {
return 'user:' + this.token + '@';
}
}]);
return AppToken;
}();
function client(cozy, clientParams) {
if (!clientParams) {
clientParams = cozy._clientParams;
}
if (clientParams instanceof Client) {
return clientParams;
}
return new Client(clientParams);
}
function registerClient(cozy, clientParams) {
var cli = client(cozy, clientParams);
if (cli.isRegistered()) {
return Promise.reject(new Error('Client already registered'));
}
return (0, _fetch.cozyFetchJSON)(cozy, 'POST', '/auth/register', cli.toRegisterJSON(), {
disableAuth: true
}).then(function (data) {
return new Client(data);
});
}
function updateClient(cozy, clientParams) {
var resetSecret = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
var cli = client(cozy, clientParams);
if (!cli.isRegistered()) {
return Promise.reject(new Error('Client not registered'));
}
var data = cli.toRegisterJSON();
data.client_id = cli.clientID;
if (resetSecret) data.client_secret = cli.clientSecret;
return (0, _fetch.cozyFetchJSON)(cozy, 'PUT', '/auth/register/' + cli.clientID, data, {
manualAuthCredentials: {
token: cli
}
}).then(function (data) {
return createClient(data, cli);
});
}
function unregisterClient(cozy, clientParams) {
var cli = client(cozy, clientParams);
if (!cli.isRegistered()) {
return Promise.reject(new Error('Client not registered'));
}
return (0, _fetch.cozyFetchJSON)(cozy, 'DELETE', '/auth/register/' + cli.clientID, null, {
manualAuthCredentials: {
token: cli
}
});
}
// getClient will retrive the registered client informations from the server.
function getClient(cozy, clientParams) {
var cli = client(cozy, clientParams);
if (!cli.isRegistered()) {
return Promise.reject(new Error('Client not registered'));
}
if ((0, _utils.isOffline)()) {
return Promise.resolve(cli);
}
return (0, _fetch.cozyFetchJSON)(cozy, 'GET', '/auth/register/' + cli.clientID, null, {
manualAuthCredentials: {
token: cli
}
}).then(function (data) {
return createClient(data, cli);
}).catch(function (err) {
// If we fall into an error while fetching the client (because of a
// bad connectivity for instance), we do not bail the whole process
// since the client should be able to continue with the persisted
// client and token.
//
// If it is an explicit Unauthorized error though, we bail, clear th
// cache and retry.
if (_fetch.FetchError.isUnauthorized(err) || _fetch.FetchError.isNotFound(err)) {
throw new Error('Client has been revoked');
}
throw err;
});
}
// createClient returns a new Client instance given on object containing the
// data of the client, from the API, and an old instance of the client.
function createClient(data, oldClient) {
var newClient = new Client(data);
// we need to keep track of the registrationAccessToken since it is send
// only on registration. The GET /auth/register/:client-id endpoint does
// not return this token.
var shouldPassRegistration = !!oldClient && oldClient.registrationAccessToken !== '' && newClient.registrationAccessToken === '';
if (shouldPassRegistration) {
newClient.registrationAccessToken = oldClient.registrationAccessToken;
}
return newClient;
}
// getAuthCodeURL returns a pair {authURL,state} given a registered client. The
// state should be stored in order to be checked against on the user validation
// phase.
function getAuthCodeURL(cozy, client) {
var scopes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
if (!(client instanceof Client)) {
client = new Client(client);
}
if (!client.isRegistered()) {
throw new Error('Client not registered');
}
var state = generateRandomState();
var query = {
client_id: client.clientID,
redirect_uri: client.redirectURI,
state: state,
response_type: 'code',
scope: scopes.join(' ')
};
return {
url: cozy._url + ('/auth/authorize?' + (0, _utils.encodeQuery)(query)),
state: state
};
}
// getAccessToken perform a request on the access_token entrypoint with the
// authorization_code grant type in order to generate a new access token for a
// newly registered client.
//
// This method extracts the access code and state from the given URL. By
// default it uses window.location.href. Also, it checks the given state with
// the one specified in the URL query parameter to prevent CSRF attacks.
function getAccessToken(cozy, client, state) {
var pageURL = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '';
if (!state) {
return Promise.reject(new Error('Missing state value'));
}
var grantQueries = getGrantCodeFromPageURL(pageURL);
if (grantQueries === null) {
return Promise.reject(new Error('Missing states from current URL'));
}
if (state !== grantQueries.state) {
return Promise.reject(new Error('Given state does not match url query state'));
}
return retrieveToken(cozy, client, null, {
grant_type: 'authorization_code',
code: grantQueries.code
});
}
// refreshToken perform a request on the access_token entrypoint with the
// refresh_token grant type in order to refresh the given token.
function refreshToken(cozy, client, token) {
return retrieveToken(cozy, client, token, {
grant_type: 'refresh_token',
refresh_token: token.refreshToken
});
}
// oauthFlow performs the stateful registration and access granting of an OAuth
// client.
function oauthFlow(cozy, storage, clientParams, onRegistered) {
var ignoreCachedCredentials = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
if (ignoreCachedCredentials) {
return storage.clear().then(function () {
return oauthFlow(cozy, storage, clientParams, onRegistered, false);
});
}
var tryCount = 0;
function clearAndRetry(err) {
if (tryCount++ > 0) {
throw err;
}
return storage.clear().then(function () {
return oauthFlow(cozy, storage, clientParams, onRegistered);
});
}
function registerNewClient() {
return storage.clear().then(function () {
return registerClient(cozy, clientParams);
}).then(function (client) {
var _getAuthCodeURL = getAuthCodeURL(cozy, client, clientParams.scopes),
url = _getAuthCodeURL.url,
state = _getAuthCodeURL.state;
return storage.save(StateKey, { client: client, url: url, state: state });
});
}
return Promise.all([storage.load(CredsKey), storage.load(StateKey)]).then(function (_ref) {
var _ref2 = _slicedToArray(_ref, 2),
credentials = _ref2[0],
storedState = _ref2[1];
// If credentials are cached we re-fetch the registered client with the
// said token. Fetching the client, if the token is outdated we should try
// the token is refreshed.
if (credentials) {
var oldClient = void 0,
_token = void 0;
try {
oldClient = new Client(credentials.client);
_token = new AccessToken(credentials.token);
} catch (err) {
// bad cache, we should clear and retry the process
return clearAndRetry(err);
}
return getClient(cozy, oldClient).then(function (client) {
return { client: client, token: _token };
}).catch(function (err) {
// If we fall into an error while fetching the client (because of a
// bad connectivity for instance), we do not bail the whole process
// since the client should be able to continue with the persisted
// client and token.
//
// If it is an explicit Unauthorized error though, we bail, clear th
// cache and retry.
if (_fetch.FetchError.isUnauthorized(err) || _fetch.FetchError.isNotFound(err)) {
throw new Error('Client has been revoked');
}
return { client: oldClient, token: _token };
});
}
// Otherwise register a new client if necessary (ie. no client is stored)
// and call the onRegistered callback to wait for the user to grant the
// access. Finally fetches to access token on success.
var statePromise = void 0;
if (!storedState) {
statePromise = registerNewClient();
} else {
statePromise = Promise.resolve(storedState);
}
var client = void 0,
state = void 0,
token = void 0;
return statePromise.then(function (data) {
client = data.client;
state = data.state;
return Promise.resolve(onRegistered(client, data.url));
}).then(function (pageURL) {
return getAccessToken(cozy, client, state, pageURL);
}).then(function (t) {
token = t;
}).then(function () {
return storage.delete(StateKey);
}).then(function () {
return { client: client, token: token };
});
}).then(function (creds) {
return storage.save(CredsKey, creds);
}, function (err) {
if (_fetch.FetchError.isUnauthorized(err)) {
return clearAndRetry(err);
} else {
throw err;
}
});
}
// retrieveToken perform a request on the access_token entrypoint in order to
// fetch a token.
function retrieveToken(cozy, client, token, query) {
if (!(client instanceof Client)) {
client = new Client(client);
}
if (!client.isRegistered()) {
return Promise.reject(new Error('Client not registered'));
}
var body = (0, _utils.encodeQuery)(Object.assign({}, query, {
client_id: client.clientID,
client_secret: client.clientSecret
}));
return (0, _fetch.cozyFetchJSON)(cozy, 'POST', '/auth/access_token', body, {
disableAuth: token === null,
dontRetry: true,
manualAuthCredentials: { client: client, token: token },
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
}).then(function (data) {
data.refreshToken = data.refreshToken || query.refresh_token;
return new AccessToken(data);
});
}
// getGrantCodeFromPageURL extract the state and code query parameters from the
// given url
function getGrantCodeFromPageURL() {
var pageURL = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
if (pageURL === '' && typeof window !== 'undefined') {
pageURL = window.location.href;
}
var queries = (0, _utils.decodeQuery)(pageURL);
if (!queries.hasOwnProperty('state')) {
return null;
}
return {
state: queries['state'],
code: queries['code']
};
}
// generateRandomState will try to generate a 128bits random value from a secure
// pseudo random generator. It will fallback on Math.random if it cannot find
// such generator.
function generateRandomState() {
var buffer = void 0;
if (typeof window !== 'undefined' && typeof window.crypto !== 'undefined' && typeof window.crypto.getRandomValues === 'function') {
buffer = new Uint8Array(StateSize);
window.crypto.getRandomValues(buffer);
} else {
try {
buffer = __webpack_require__(12).randomBytes(StateSize);
} catch (e) {
buffer = null;
}
}
if (!buffer) {
buffer = new Array(StateSize);
for (var i = 0; i < buffer.length; i++) {
buffer[i] = Math.floor(Math.random() * 255);
}
}
return btoa(String.fromCharCode.apply(null, buffer)).replace(/=+$/, '').replace(/\//g, '_').replace(/\+/g, '-');
}
/* WEBPACK VAR INJECTION */}.call(exports, __webpack_require__(6)))
/***/ }),
/* 4 */
/***/ (function(module, exports) {
module.exports = require("babel-runtime/regenerator");
/***/ }),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.pickService = pickService;
// helper to serialize/deserialize an error for/from postMessage
var errorSerializer = exports.errorSerializer = function () {
function mapErrorProperties(from, to) {
var result = Object.assign(to, from);
var nativeProperties = ['name', 'message'];
return nativeProperties.reduce(function (result, property) {
if (from[property]) {
to[property] = from[property];
}
return result;
}, result);
}
return {
serialize: function serialize(error) {
return mapErrorProperties(error, {});
},
deserialize: function deserialize(data) {
return mapErrorProperties(data, new Error(data.message));
}
};
}();
var first = function first(arr) {
return arr && arr[0];
};
// In a far future, the user will have to pick the desired service from a list.
// For now it's our job, an easy job as we arbitrary pick the first service of
// the list.
function pickService(intent, filterServices) {
var services = intent.attributes.services;
var filteredServices = filterServices ? (services || []).filter(filterServices) : services;
return first(filteredServices);
}
/***/ }),
/* 6 */
/***/ (function(module, exports) {
module.exports = require("btoa");
/***/ }),
/* 7 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
function indexKey(doc) {
return doc.type + '/' + doc.id;
}
function findByRef(resources, ref) {
return resources[indexKey(ref)];
}
function handleResource(rawResource, resources, links) {
var resource = {
_id: rawResource.id,
_type: rawResource.type,
_rev: rawResource.meta && rawResource.meta.rev,
links: Object.assign({}, rawResource.links, links),
attributes: rawResource.attributes,
relations: function relations(name) {
var rels = rawResource.relationships[name];
if (rels === undefined || rels.data === undefined) return undefined;
if (rels.data === null) return null;
if (!Array.isArray(rels.data)) return findByRef(resources, rels.data);
return rels.data.map(function (ref) {
return findByRef(resources, ref);
});
}
};
if (rawResource.relationships) {
resource.relationships = rawResource.relationships;
}
resources[indexKey(rawResource)] = resource;
return resource;
}
function handleTopLevel(doc) {
var resources = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
// build an index of included resource by Type & ID
var included = doc.included;
if (Array.isArray(included)) {
included.forEach(function (r) {
return handleResource(r, resources, doc.links);
});
}
if (Array.isArray(doc.data)) {
return doc.data.map(function (r) {
return handleResource(r, resources, doc.links);
});
} else {
return handleResource(doc.data, resources, doc.links);
}
}
exports.default = handleTopLevel;
/***/ }),
/* 8 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.redirect = exports.getRedirectionURL = undefined;
var _regenerator = __webpack_require__(4);
var _regenerator2 = _interopRequireDefault(_regenerator);
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
// Redirect to an app able to handle the doctype
// Redirections are more or less a hack of the intent API to retrieve an URL for
// accessing a given doctype or a given document.
// It needs to use a special action `REDIRECT`
var getRedirectionURL = exports.getRedirectionURL = function () {
var _ref = _asyncToGenerator( /*#__PURE__*/_regenerator2.default.mark(function _callee(cozy, type, data) {
var intent, service, baseURL;
return _regenerator2.default.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
if (!(!type && !data)) {
_context.next = 2;
break;
}
throw new Error('Cannot retrieve redirection, at least type or doc must be provided');
case 2:
_context.next = 4;
return create(cozy, 'REDIRECT', type, data);
case 4:
intent = _context.sent;
service = (0, _helpers.pickService)(intent);
if (service) {
_context.next = 8;
break;
}
throw new Error('Unable to find a service');
case 8:
// Intents cannot be deleted now
// await deleteIntent(cozy, intent)
baseURL = removeQueryString(service.href);
return _context.abrupt('return', data ? buildRedirectionURL(baseURL, data) : baseURL);
case 10:
case 'end':
return _context.stop();
}
}
}, _callee, this);
}));
return function getRedirectionURL(_x3, _x4, _x5) {
return _ref.apply(this, arguments);
};
}();
var redirect = exports.redirect = function () {
var _ref2 = _asyncToGenerator( /*#__PURE__*/_regenerator2.default.mark(function _callee2(cozy, type, doc, redirectFn) {
var redirectionURL;
return _regenerator2.default.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
if (window) {
_context2.next = 2;
break;
}
throw new Error('redirect() method can only be called in a browser');
case 2:
_context2.next = 4;
return getRedirectionURL(cozy, type, doc);
case 4:
redirectionURL = _context2.sent;
if (!(redirectFn && typeof redirectFn === 'function')) {
_context2.next = 7;
break;
}
return _context2.abrupt('return', redirectFn(redirectionURL));
case 7:
window.location.href = redirectionURL;
case 8:
case 'end':
return _context2.stop();
}
}
}, _callee2, this);
}));
return function redirect(_x6, _x7, _x8, _x9) {
return _ref2.apply(this, arguments);
};
}();
exports.create = create;
exports.createService = createService;
var _fetch = __webpack_require__(0);
var _helpers = __webpack_require__(5);
var _client = __webpack_require__(16);
var client = _interopRequireWildcard(_client);
var _service = __webpack_require__(17);
var service = _interopRequireWildcard(_service);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
function create(cozy, action, type) {
var data = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
var permissions = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : [];
if (!action) throw new Error('Misformed intent, "action" property must be provided');
if (!type) throw new Error('Misformed intent, "type" property must be provided');
var createPromise = (0, _fetch.cozyFetchJSON)(cozy, 'POST', '/intents', {
data: {
type: 'io.cozy.intents',
attributes: {
action: action,
type: type,
data: data,
permissions: permissions
}
}
});
createPromise.start = function (element, onReadyCallback) {
var options = {
filteredServices: data.filteredServices,
onReadyCallback: onReadyCallback
};
delete data.filteredServices;
return createPromise.then(function (intent) {
return client.start(cozy, intent, element, data, options);
});
};
return createPromise;
}
// returns a service to communicate with intent client
function createService(cozy, intentId, serviceWindow) {
return service.start(cozy, intentId, serviceWindow);
}
function removeQueryString(url) {
return url.replace(/\?[^/#]*/, '');
}
function isSerializable(value) {
return !['object', 'function'].includes(typeof value === 'undefined' ? 'undefined' : _typeof(value));
}
function buildRedirectionURL(url, data) {
var parameterStrings = Object.keys(data).filter(function (key) {
return isSerializable(data[key]);
}).map(function (key) {
return key + '=' + data[key];
});
return parameterStrings.length ? url + '?' + parameterStrings.join('&') : url;
}
/***/ }),
/* 9 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* global fetch URL */
var _utils = __webpack_require__(1);
var _auth_storage = __webpack_require__(10);
var _auth_v = __webpack_require__(11);
var _auth_v2 = __webpack_require__(3);
var auth = _interopRequireWildcard(_auth_v2);
var _data = __webpack_require__(13);
var data = _interopRequireWildcard(_data);
var _fetch2 = __webpack_require__(0);
var cozyFetch = _interopRequireWildcard(_fetch2);
var _mango = __webpack_require__(14);
var mango = _interopRequireWildcard(_mango);
var _files = __webpack_require__(15);
var files = _interopRequireWildcard(_files);
var _intents = __webpack_require__(8);
var intents = _interopRequireWildcard(_intents);
var _jobs = __webpack_require__(18);
var jobs = _interopRequireWildcard(_jobs);
var _offline = __webpack_require__(19);
var offline = _interopRequireWildcard(_offline);
var _settings = __webpack_require__(23);
var settings = _interopRequireWildcard(_settings);
var _relations = __webpack_require__(24);
var relations = _interopRequireWildcard(_relations);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var AppTokenV3 = auth.AppToken,
AccessTokenV3 = auth.AccessToken,
ClientV3 = auth.Client;
var AuthNone = 0;
var AuthRunning = 1;
var AuthError = 2;
var AuthOK = 3;
var defaultClientParams = {
softwareID: 'github.com/cozy/cozy-client-js'
};
var dataProto = {
create: data.create,
find: data.find,
findMany: data.findMany,
findAll: data.findAll,
update: data.update,
delete: data._delete,
updateAttributes: data.updateAttributes,
changesFeed: data.changesFeed,
defineIndex: mango.defineIndex,
query: mango.query,
addReferencedFiles: relations.addReferencedFiles,
removeReferencedFiles: relations.removeReferencedFiles,
listReferencedFiles: relations.listReferencedFiles,
fetchReferencedFiles: relations.fetchReferencedFiles,
destroy: function destroy() {
(0, _utils.warn)('destroy is deprecated, use cozy.data.delete instead.');
return data._delete.apply(data, arguments);
}
};
var authProto = {
client: auth.client,
registerClient: auth.registerClient,
updateClient: auth.updateClient,
unregisterClient: auth.unregisterClient,
getClient: auth.getClient,
getAuthCodeURL: auth.getAuthCodeURL,
getAccessToken: auth.getAccessToken,
refreshToken: auth.refreshToken
};
var filesProto = {
create: files.create,
createDirectory: files.createDirectory,
createDirectoryByPath: files.createDirectoryByPath,
updateById: files.updateById,
updateAttributesById: files.updateAttributesById,
updateAttributesByPath: files.updateAttributesByPath,
trashById: files.trashById,
statById: files.statById,
statByPath: files.statByPath,
downloadById: files.downloadById,
downloadByPath: files.downloadByPath,
getDownloadLinkById: files.getDownloadLinkById,
getDownloadLink: files.getDownloadLinkByPath, // DEPRECATED, should be removed very soon
getDownloadLinkByPath: files.getDownloadLinkByPath,
getArchiveLink: function getArchiveLink() {
(0, _utils.warn)('getArchiveLink is deprecated, use cozy.files.getArchiveLinkByPaths instead.');
return files.getArchiveLinkByPaths.apply(files, arguments);
},
getArchiveLinkByPaths: files.getArchiveLinkByPaths,
getArchiveLinkByIds: files.getArchiveLinkByIds,
getFilePath: files.getFilePath,
getCollectionShareLink: files.getCollectionShareLink,
query: mango.queryFiles,
listTrash: files.listTrash,
clearTrash: files.clearTrash,
restoreById: files.restoreById,
destroyById: files.destroyById
};
var intentsProto = {
create: intents.create,
createService: intents.createService,
getRedirectionURL: intents.getRedirectionURL,
redirect: intents.redirect
};
var jobsProto = {
create: jobs.create,
count: jobs.count,
queued: jobs.queued
};
var offlineProto = {
init: offline.init,
getDoctypes: offline.getDoctypes,
// database
hasDatabase: offline.hasDatabase,
getDatabase: offline.getDatabase,
createDatabase: offline.createDatabase,
migrateDatabase: offline.migrateDatabase,
destroyDatabase: offline.destroyDatabase,
destroyAllDatabase: offline.destroyAllDatabase,
// replication
hasReplication: offline.hasReplication,
replicateFromCozy: offline.replicateFromCozy,
stopReplication: offline.stopReplication,
stopAllReplication: offline.stopAllReplication,
// repeated replication
hasRepeatedReplication: offline.hasRepeatedReplication,
startRepeatedReplication: offline.startRepeatedReplication,
stopRepeatedReplication: offline.stopRepeatedReplication,
stopAllRepeatedReplication: offline.stopAllRepeatedReplication
};
var settingsProto = {
diskUsage: settings.diskUsage,
changePassphrase: settings.changePassphrase,
getInstance: settings.getInstance,
updateInstance: settings.updateInstance,
getClients: settings.getClients,
deleteClientById: settings.deleteClientById,
updateLastSync: settings.updateLastSync
};
var ensureHasReconnectParam = function ensureHasReconnectParam(_url) {
var url = new URL(_url);
if (url.searchParams && !url.searchParams.has('reconnect')) {
url.searchParams.append('reconnect', 1);
} else if (!url.search || url.search.indexOf('reconnect') === -1) {
// Some old navigators do not have the searchParams API
// and it is not polyfilled by babel-polyfill
url.search = url.search + '&reconnect=1';
}
return url.toString();
};
var Client = function () {
function Client(options) {
_classCallCheck(this, Client);
this.data = {};
this.files = {};
this.intents = {};
this.jobs = {};
this.offline = {};
this.settings = {};
this.auth = {
Client: ClientV3,
AccessToken: AccessTokenV3,
AppToken: AppTokenV3,
AppTokenV2: _auth_v.AppToken,
LocalStorage: _auth_storage.LocalStorage,
MemoryStorage: _auth_storage.MemoryStorage
};
this._inited = false;
if (options) {
this.init(options);
}
}
_createClass(Client, [{
key: 'init',
value: functi