UNPKG

cozy-client-js

Version:

Javascript library to interact with a cozy

1,542 lines (1,292 loc) 130 kB
(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