summaryrefslogtreecommitdiffstats
path: root/services/common/kinto-http-client.js
diff options
context:
space:
mode:
Diffstat (limited to 'services/common/kinto-http-client.js')
-rw-r--r--services/common/kinto-http-client.js1891
1 files changed, 1891 insertions, 0 deletions
diff --git a/services/common/kinto-http-client.js b/services/common/kinto-http-client.js
new file mode 100644
index 000000000..57f6946d1
--- /dev/null
+++ b/services/common/kinto-http-client.js
@@ -0,0 +1,1891 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This file is generated from kinto-http.js - do not modify directly.
+ */
+
+this.EXPORTED_SYMBOLS = ["KintoHttpClient"];
+
+/*
+ * Version 2.0.0 - 61435f3
+ */
+
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.KintoHttpClient = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.default = undefined;
+
+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 _base = require("../src/base");
+
+var _base2 = _interopRequireDefault(_base);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Timer.jsm");
+Cu.importGlobalProperties(['fetch']);
+const { EventEmitter } = Cu.import("resource://devtools/shared/event-emitter.js", {});
+
+let KintoHttpClient = class KintoHttpClient extends _base2.default {
+ constructor(remote, options = {}) {
+ const events = {};
+ EventEmitter.decorate(events);
+ super(remote, _extends({ events }, options));
+ }
+};
+
+// This fixes compatibility with CommonJS required by browserify.
+// See http://stackoverflow.com/questions/33505992/babel-6-changes-how-it-exports-default/33683495#33683495
+
+exports.default = KintoHttpClient;
+if (typeof module === "object") {
+ module.exports = KintoHttpClient;
+}
+
+},{"../src/base":2}],2:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.default = exports.SUPPORTED_PROTOCOL_VERSION = undefined;
+
+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 _dec, _dec2, _dec3, _dec4, _dec5, _dec6, _desc, _value, _class;
+
+var _utils = require("./utils");
+
+var _http = require("./http");
+
+var _http2 = _interopRequireDefault(_http);
+
+var _endpoint = require("./endpoint");
+
+var _endpoint2 = _interopRequireDefault(_endpoint);
+
+var _requests = require("./requests");
+
+var requests = _interopRequireWildcard(_requests);
+
+var _batch = require("./batch");
+
+var _bucket = require("./bucket");
+
+var _bucket2 = _interopRequireDefault(_bucket);
+
+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 _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
+ var desc = {};
+ Object['ke' + 'ys'](descriptor).forEach(function (key) {
+ desc[key] = descriptor[key];
+ });
+ desc.enumerable = !!desc.enumerable;
+ desc.configurable = !!desc.configurable;
+
+ if ('value' in desc || desc.initializer) {
+ desc.writable = true;
+ }
+
+ desc = decorators.slice().reverse().reduce(function (desc, decorator) {
+ return decorator(target, property, desc) || desc;
+ }, desc);
+
+ if (context && desc.initializer !== void 0) {
+ desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
+ desc.initializer = undefined;
+ }
+
+ if (desc.initializer === void 0) {
+ Object['define' + 'Property'](target, property, desc);
+ desc = null;
+ }
+
+ return desc;
+}
+
+/**
+ * Currently supported protocol version.
+ * @type {String}
+ */
+const SUPPORTED_PROTOCOL_VERSION = exports.SUPPORTED_PROTOCOL_VERSION = "v1";
+
+/**
+ * High level HTTP client for the Kinto API.
+ *
+ * @example
+ * const client = new KintoClient("https://kinto.dev.mozaws.net/v1");
+ * client.bucket("default")
+* .collection("my-blog")
+* .createRecord({title: "First article"})
+ * .then(console.log.bind(console))
+ * .catch(console.error.bind(console));
+ */
+let KintoClientBase = (_dec = (0, _utils.nobatch)("This operation is not supported within a batch operation."), _dec2 = (0, _utils.nobatch)("This operation is not supported within a batch operation."), _dec3 = (0, _utils.nobatch)("This operation is not supported within a batch operation."), _dec4 = (0, _utils.nobatch)("This operation is not supported within a batch operation."), _dec5 = (0, _utils.nobatch)("Can't use batch within a batch!"), _dec6 = (0, _utils.support)("1.4", "2.0"), (_class = class KintoClientBase {
+ /**
+ * Constructor.
+ *
+ * @param {String} remote The remote URL.
+ * @param {Object} [options={}] The options object.
+ * @param {Boolean} [options.safe=true] Adds concurrency headers to every requests.
+ * @param {EventEmitter} [options.events=EventEmitter] The events handler instance.
+ * @param {Object} [options.headers={}] The key-value headers to pass to each request.
+ * @param {String} [options.bucket="default"] The default bucket to use.
+ * @param {String} [options.requestMode="cors"] The HTTP request mode (from ES6 fetch spec).
+ * @param {Number} [options.timeout=5000] The requests timeout in ms.
+ */
+ constructor(remote, options = {}) {
+ if (typeof remote !== "string" || !remote.length) {
+ throw new Error("Invalid remote URL: " + remote);
+ }
+ if (remote[remote.length - 1] === "/") {
+ remote = remote.slice(0, -1);
+ }
+ this._backoffReleaseTime = null;
+
+ /**
+ * Default request options container.
+ * @private
+ * @type {Object}
+ */
+ this.defaultReqOptions = {
+ bucket: options.bucket || "default",
+ headers: options.headers || {},
+ safe: !!options.safe
+ };
+
+ this._options = options;
+ this._requests = [];
+ this._isBatch = !!options.batch;
+
+ // public properties
+ /**
+ * The remote server base URL.
+ * @type {String}
+ */
+ this.remote = remote;
+ /**
+ * Current server information.
+ * @ignore
+ * @type {Object|null}
+ */
+ this.serverInfo = null;
+ /**
+ * The event emitter instance. Should comply with the `EventEmitter`
+ * interface.
+ * @ignore
+ * @type {Class}
+ */
+ this.events = options.events;
+
+ const { requestMode, timeout } = options;
+ /**
+ * The HTTP instance.
+ * @ignore
+ * @type {HTTP}
+ */
+ this.http = new _http2.default(this.events, { requestMode, timeout });
+ this._registerHTTPEvents();
+ }
+
+ /**
+ * The remote endpoint base URL. Setting the value will also extract and
+ * validate the version.
+ * @type {String}
+ */
+ get remote() {
+ return this._remote;
+ }
+
+ /**
+ * @ignore
+ */
+ set remote(url) {
+ let version;
+ try {
+ version = url.match(/\/(v\d+)\/?$/)[1];
+ } catch (err) {
+ throw new Error("The remote URL must contain the version: " + url);
+ }
+ if (version !== SUPPORTED_PROTOCOL_VERSION) {
+ throw new Error(`Unsupported protocol version: ${ version }`);
+ }
+ this._remote = url;
+ this._version = version;
+ }
+
+ /**
+ * The current server protocol version, eg. `v1`.
+ * @type {String}
+ */
+ get version() {
+ return this._version;
+ }
+
+ /**
+ * Backoff remaining time, in milliseconds. Defaults to zero if no backoff is
+ * ongoing.
+ *
+ * @type {Number}
+ */
+ get backoff() {
+ const currentTime = new Date().getTime();
+ if (this._backoffReleaseTime && currentTime < this._backoffReleaseTime) {
+ return this._backoffReleaseTime - currentTime;
+ }
+ return 0;
+ }
+
+ /**
+ * Registers HTTP events.
+ * @private
+ */
+ _registerHTTPEvents() {
+ // Prevent registering event from a batch client instance
+ if (!this._isBatch) {
+ this.events.on("backoff", backoffMs => {
+ this._backoffReleaseTime = backoffMs;
+ });
+ }
+ }
+
+ /**
+ * Retrieve a bucket object to perform operations on it.
+ *
+ * @param {String} name The bucket name.
+ * @param {Object} [options={}] The request options.
+ * @param {Boolean} [options.safe] The resulting safe option.
+ * @param {String} [options.bucket] The resulting bucket name option.
+ * @param {Object} [options.headers] The extended headers object option.
+ * @return {Bucket}
+ */
+ bucket(name, options = {}) {
+ const bucketOptions = (0, _utils.omit)(this._getRequestOptions(options), "bucket");
+ return new _bucket2.default(this, name, bucketOptions);
+ }
+
+ /**
+ * Generates a request options object, deeply merging the client configured
+ * defaults with the ones provided as argument.
+ *
+ * Note: Headers won't be overriden but merged with instance default ones.
+ *
+ * @private
+ * @param {Object} [options={}] The request options.
+ * @property {Boolean} [options.safe] The resulting safe option.
+ * @property {String} [options.bucket] The resulting bucket name option.
+ * @property {Object} [options.headers] The extended headers object option.
+ * @return {Object}
+ */
+ _getRequestOptions(options = {}) {
+ return _extends({}, this.defaultReqOptions, options, {
+ batch: this._isBatch,
+ // Note: headers should never be overriden but extended
+ headers: _extends({}, this.defaultReqOptions.headers, options.headers)
+ });
+ }
+
+ /**
+ * Retrieves server information and persist them locally. This operation is
+ * usually performed a single time during the instance lifecycle.
+ *
+ * @param {Object} [options={}] The request options.
+ * @return {Promise<Object, Error>}
+ */
+ fetchServerInfo(options = {}) {
+ if (this.serverInfo) {
+ return Promise.resolve(this.serverInfo);
+ }
+ return this.http.request(this.remote + (0, _endpoint2.default)("root"), {
+ headers: _extends({}, this.defaultReqOptions.headers, options.headers)
+ }).then(({ json }) => {
+ this.serverInfo = json;
+ return this.serverInfo;
+ });
+ }
+
+ /**
+ * Retrieves Kinto server settings.
+ *
+ * @param {Object} [options={}] The request options.
+ * @return {Promise<Object, Error>}
+ */
+
+ fetchServerSettings(options = {}) {
+ return this.fetchServerInfo(options).then(({ settings }) => settings);
+ }
+
+ /**
+ * Retrieve server capabilities information.
+ *
+ * @param {Object} [options={}] The request options.
+ * @return {Promise<Object, Error>}
+ */
+
+ fetchServerCapabilities(options = {}) {
+ return this.fetchServerInfo(options).then(({ capabilities }) => capabilities);
+ }
+
+ /**
+ * Retrieve authenticated user information.
+ *
+ * @param {Object} [options={}] The request options.
+ * @return {Promise<Object, Error>}
+ */
+
+ fetchUser(options = {}) {
+ return this.fetchServerInfo(options).then(({ user }) => user);
+ }
+
+ /**
+ * Retrieve authenticated user information.
+ *
+ * @param {Object} [options={}] The request options.
+ * @return {Promise<Object, Error>}
+ */
+
+ fetchHTTPApiVersion(options = {}) {
+ return this.fetchServerInfo(options).then(({ http_api_version }) => {
+ return http_api_version;
+ });
+ }
+
+ /**
+ * Process batch requests, chunking them according to the batch_max_requests
+ * server setting when needed.
+ *
+ * @param {Array} requests The list of batch subrequests to perform.
+ * @param {Object} [options={}] The options object.
+ * @return {Promise<Object, Error>}
+ */
+ _batchRequests(requests, options = {}) {
+ const headers = _extends({}, this.defaultReqOptions.headers, options.headers);
+ if (!requests.length) {
+ return Promise.resolve([]);
+ }
+ return this.fetchServerSettings().then(serverSettings => {
+ const maxRequests = serverSettings["batch_max_requests"];
+ if (maxRequests && requests.length > maxRequests) {
+ const chunks = (0, _utils.partition)(requests, maxRequests);
+ return (0, _utils.pMap)(chunks, chunk => this._batchRequests(chunk, options));
+ }
+ return this.execute({
+ path: (0, _endpoint2.default)("batch"),
+ method: "POST",
+ headers: headers,
+ body: {
+ defaults: { headers },
+ requests: requests
+ }
+ })
+ // we only care about the responses
+ .then(({ responses }) => responses);
+ });
+ }
+
+ /**
+ * Sends batch requests to the remote server.
+ *
+ * Note: Reserved for internal use only.
+ *
+ * @ignore
+ * @param {Function} fn The function to use for describing batch ops.
+ * @param {Object} [options={}] The options object.
+ * @param {Boolean} [options.safe] The safe option.
+ * @param {String} [options.bucket] The bucket name option.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Boolean} [options.aggregate=false] Produces an aggregated result object.
+ * @return {Promise<Object, Error>}
+ */
+
+ batch(fn, options = {}) {
+ const rootBatch = new KintoClientBase(this.remote, _extends({}, this._options, this._getRequestOptions(options), {
+ batch: true
+ }));
+ let bucketBatch, collBatch;
+ if (options.bucket) {
+ bucketBatch = rootBatch.bucket(options.bucket);
+ if (options.collection) {
+ collBatch = bucketBatch.collection(options.collection);
+ }
+ }
+ const batchClient = collBatch || bucketBatch || rootBatch;
+ try {
+ fn(batchClient);
+ } catch (err) {
+ return Promise.reject(err);
+ }
+ return this._batchRequests(rootBatch._requests, options).then(responses => {
+ if (options.aggregate) {
+ return (0, _batch.aggregate)(responses, rootBatch._requests);
+ }
+ return responses;
+ });
+ }
+
+ /**
+ * Executes an atomic HTTP request.
+ *
+ * @private
+ * @param {Object} request The request object.
+ * @param {Object} [options={}] The options object.
+ * @param {Boolean} [options.raw=false] If true, resolve with full response object, including json body and headers instead of just json.
+ * @return {Promise<Object, Error>}
+ */
+ execute(request, options = { raw: false }) {
+ // If we're within a batch, add the request to the stack to send at once.
+ if (this._isBatch) {
+ this._requests.push(request);
+ // Resolve with a message in case people attempt at consuming the result
+ // from within a batch operation.
+ const msg = "This result is generated from within a batch " + "operation and should not be consumed.";
+ return Promise.resolve(options.raw ? { json: msg } : msg);
+ }
+ const promise = this.fetchServerSettings().then(_ => {
+ return this.http.request(this.remote + request.path, _extends({}, request, {
+ body: JSON.stringify(request.body)
+ }));
+ });
+ return options.raw ? promise : promise.then(({ json }) => json);
+ }
+
+ /**
+ * Retrieves the list of buckets.
+ *
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @return {Promise<Object[], Error>}
+ */
+ listBuckets(options = {}) {
+ return this.execute({
+ path: (0, _endpoint2.default)("bucket"),
+ headers: _extends({}, this.defaultReqOptions.headers, options.headers)
+ });
+ }
+
+ /**
+ * Creates a new bucket on the server.
+ *
+ * @param {String} id The bucket name.
+ * @param {Object} [options={}] The options object.
+ * @param {Boolean} [options.data] The bucket data option.
+ * @param {Boolean} [options.safe] The safe option.
+ * @param {Object} [options.headers] The headers object option.
+ * @return {Promise<Object, Error>}
+ */
+ createBucket(id, options = {}) {
+ if (!id) {
+ throw new Error("A bucket id is required.");
+ }
+ // Note that we simply ignore any "bucket" option passed here, as the one
+ // we're interested in is the one provided as a required argument.
+ const reqOptions = this._getRequestOptions(options);
+ const { data = {}, permissions } = reqOptions;
+ data.id = id;
+ const path = (0, _endpoint2.default)("bucket", id);
+ return this.execute(requests.createRequest(path, { data, permissions }, reqOptions));
+ }
+
+ /**
+ * Deletes a bucket from the server.
+ *
+ * @ignore
+ * @param {Object|String} bucket The bucket to delete.
+ * @param {Object} [options={}] The options object.
+ * @param {Boolean} [options.safe] The safe option.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Number} [options.last_modified] The last_modified option.
+ * @return {Promise<Object, Error>}
+ */
+ deleteBucket(bucket, options = {}) {
+ const bucketObj = (0, _utils.toDataBody)(bucket);
+ if (!bucketObj.id) {
+ throw new Error("A bucket id is required.");
+ }
+ const path = (0, _endpoint2.default)("bucket", bucketObj.id);
+ const { last_modified } = { bucketObj };
+ const reqOptions = this._getRequestOptions(_extends({ last_modified }, options));
+ return this.execute(requests.deleteRequest(path, reqOptions));
+ }
+
+ /**
+ * Deletes all buckets on the server.
+ *
+ * @ignore
+ * @param {Object} [options={}] The options object.
+ * @param {Boolean} [options.safe] The safe option.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Number} [options.last_modified] The last_modified option.
+ * @return {Promise<Object, Error>}
+ */
+
+ deleteBuckets(options = {}) {
+ const reqOptions = this._getRequestOptions(options);
+ const path = (0, _endpoint2.default)("bucket");
+ return this.execute(requests.deleteRequest(path, reqOptions));
+ }
+}, (_applyDecoratedDescriptor(_class.prototype, "fetchServerSettings", [_dec], Object.getOwnPropertyDescriptor(_class.prototype, "fetchServerSettings"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "fetchServerCapabilities", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, "fetchServerCapabilities"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "fetchUser", [_dec3], Object.getOwnPropertyDescriptor(_class.prototype, "fetchUser"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "fetchHTTPApiVersion", [_dec4], Object.getOwnPropertyDescriptor(_class.prototype, "fetchHTTPApiVersion"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "batch", [_dec5], Object.getOwnPropertyDescriptor(_class.prototype, "batch"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "deleteBuckets", [_dec6], Object.getOwnPropertyDescriptor(_class.prototype, "deleteBuckets"), _class.prototype)), _class));
+exports.default = KintoClientBase;
+
+},{"./batch":3,"./bucket":4,"./endpoint":6,"./http":8,"./requests":9,"./utils":10}],3:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.aggregate = aggregate;
+/**
+ * Exports batch responses as a result object.
+ *
+ * @private
+ * @param {Array} responses The batch subrequest responses.
+ * @param {Array} requests The initial issued requests.
+ * @return {Object}
+ */
+function aggregate(responses = [], requests = []) {
+ if (responses.length !== requests.length) {
+ throw new Error("Responses length should match requests one.");
+ }
+ const results = {
+ errors: [],
+ published: [],
+ conflicts: [],
+ skipped: []
+ };
+ return responses.reduce((acc, response, index) => {
+ const { status } = response;
+ if (status >= 200 && status < 400) {
+ acc.published.push(response.body);
+ } else if (status === 404) {
+ acc.skipped.push(response.body);
+ } else if (status === 412) {
+ acc.conflicts.push({
+ // XXX: specifying the type is probably superfluous
+ type: "outgoing",
+ local: requests[index].body,
+ remote: response.body.details && response.body.details.existing || null
+ });
+ } else {
+ acc.errors.push({
+ path: response.path,
+ sent: requests[index],
+ error: response.body
+ });
+ }
+ return acc;
+ }, results);
+}
+
+},{}],4:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.default = undefined;
+
+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 _utils = require("./utils");
+
+var _collection = require("./collection");
+
+var _collection2 = _interopRequireDefault(_collection);
+
+var _requests = require("./requests");
+
+var requests = _interopRequireWildcard(_requests);
+
+var _endpoint = require("./endpoint");
+
+var _endpoint2 = _interopRequireDefault(_endpoint);
+
+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 }; }
+
+/**
+ * Abstract representation of a selected bucket.
+ *
+ */
+let Bucket = class Bucket {
+ /**
+ * Constructor.
+ *
+ * @param {KintoClient} client The client instance.
+ * @param {String} name The bucket name.
+ * @param {Object} [options={}] The headers object option.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Boolean} [options.safe] The safe option.
+ */
+ constructor(client, name, options = {}) {
+ /**
+ * @ignore
+ */
+ this.client = client;
+ /**
+ * The bucket name.
+ * @type {String}
+ */
+ this.name = name;
+ /**
+ * The default options object.
+ * @ignore
+ * @type {Object}
+ */
+ this.options = options;
+ /**
+ * @ignore
+ */
+ this._isBatch = !!options.batch;
+ }
+
+ /**
+ * Merges passed request options with default bucket ones, if any.
+ *
+ * @private
+ * @param {Object} [options={}] The options to merge.
+ * @return {Object} The merged options.
+ */
+ _bucketOptions(options = {}) {
+ const headers = _extends({}, this.options && this.options.headers, options.headers);
+ return _extends({}, this.options, options, {
+ headers,
+ bucket: this.name,
+ batch: this._isBatch
+ });
+ }
+
+ /**
+ * Selects a collection.
+ *
+ * @param {String} name The collection name.
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Boolean} [options.safe] The safe option.
+ * @return {Collection}
+ */
+ collection(name, options = {}) {
+ return new _collection2.default(this.client, this, name, this._bucketOptions(options));
+ }
+
+ /**
+ * Retrieves bucket data.
+ *
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @return {Promise<Object, Error>}
+ */
+ getData(options = {}) {
+ return this.client.execute({
+ path: (0, _endpoint2.default)("bucket", this.name),
+ headers: _extends({}, this.options.headers, options.headers)
+ }).then(res => res.data);
+ }
+
+ /**
+ * Set bucket data.
+ * @param {Object} data The bucket data object.
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Boolean} [options.safe] The safe option.
+ * @param {Boolean} [options.patch] The patch option.
+ * @param {Number} [options.last_modified] The last_modified option.
+ * @return {Promise<Object, Error>}
+ */
+ setData(data, options = {}) {
+ if (!(0, _utils.isObject)(data)) {
+ throw new Error("A bucket object is required.");
+ }
+
+ const bucket = _extends({}, data, { id: this.name });
+
+ // For default bucket, we need to drop the id from the data object.
+ // Bug in Kinto < 3.1.1
+ const bucketId = bucket.id;
+ if (bucket.id === "default") {
+ delete bucket.id;
+ }
+
+ const path = (0, _endpoint2.default)("bucket", bucketId);
+ const { permissions } = options;
+ const reqOptions = _extends({}, this._bucketOptions(options));
+ const request = requests.updateRequest(path, { data: bucket, permissions }, reqOptions);
+ return this.client.execute(request);
+ }
+
+ /**
+ * Retrieves the list of collections in the current bucket.
+ *
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @return {Promise<Array<Object>, Error>}
+ */
+ listCollections(options = {}) {
+ return this.client.execute({
+ path: (0, _endpoint2.default)("collection", this.name),
+ headers: _extends({}, this.options.headers, options.headers)
+ });
+ }
+
+ /**
+ * Creates a new collection in current bucket.
+ *
+ * @param {String|undefined} id The collection id.
+ * @param {Object} [options={}] The options object.
+ * @param {Boolean} [options.safe] The safe option.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Object} [options.permissions] The permissions object.
+ * @param {Object} [options.data] The data object.
+ * @return {Promise<Object, Error>}
+ */
+ createCollection(id, options = {}) {
+ const reqOptions = this._bucketOptions(options);
+ const { permissions, data = {} } = reqOptions;
+ data.id = id;
+ const path = (0, _endpoint2.default)("collection", this.name, id);
+ const request = requests.createRequest(path, { data, permissions }, reqOptions);
+ return this.client.execute(request);
+ }
+
+ /**
+ * Deletes a collection from the current bucket.
+ *
+ * @param {Object|String} collection The collection to delete.
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Boolean} [options.safe] The safe option.
+ * @param {Number} [options.last_modified] The last_modified option.
+ * @return {Promise<Object, Error>}
+ */
+ deleteCollection(collection, options = {}) {
+ const collectionObj = (0, _utils.toDataBody)(collection);
+ if (!collectionObj.id) {
+ throw new Error("A collection id is required.");
+ }
+ const { id, last_modified } = collectionObj;
+ const reqOptions = this._bucketOptions(_extends({ last_modified }, options));
+ const path = (0, _endpoint2.default)("collection", this.name, id);
+ const request = requests.deleteRequest(path, reqOptions);
+ return this.client.execute(request);
+ }
+
+ /**
+ * Retrieves the list of groups in the current bucket.
+ *
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @return {Promise<Array<Object>, Error>}
+ */
+ listGroups(options = {}) {
+ return this.client.execute({
+ path: (0, _endpoint2.default)("group", this.name),
+ headers: _extends({}, this.options.headers, options.headers)
+ });
+ }
+
+ /**
+ * Creates a new group in current bucket.
+ *
+ * @param {String} id The group id.
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @return {Promise<Object, Error>}
+ */
+ getGroup(id, options = {}) {
+ return this.client.execute({
+ path: (0, _endpoint2.default)("group", this.name, id),
+ headers: _extends({}, this.options.headers, options.headers)
+ });
+ }
+
+ /**
+ * Creates a new group in current bucket.
+ *
+ * @param {String|undefined} id The group id.
+ * @param {Array<String>} [members=[]] The list of principals.
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.data] The data object.
+ * @param {Object} [options.permissions] The permissions object.
+ * @param {Boolean} [options.safe] The safe option.
+ * @param {Object} [options.headers] The headers object option.
+ * @return {Promise<Object, Error>}
+ */
+ createGroup(id, members = [], options = {}) {
+ const reqOptions = this._bucketOptions(options);
+ const data = _extends({}, options.data, {
+ id,
+ members
+ });
+ const path = (0, _endpoint2.default)("group", this.name, id);
+ const { permissions } = options;
+ const request = requests.createRequest(path, { data, permissions }, reqOptions);
+ return this.client.execute(request);
+ }
+
+ /**
+ * Updates an existing group in current bucket.
+ *
+ * @param {Object} group The group object.
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.data] The data object.
+ * @param {Object} [options.permissions] The permissions object.
+ * @param {Boolean} [options.safe] The safe option.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Number} [options.last_modified] The last_modified option.
+ * @return {Promise<Object, Error>}
+ */
+ updateGroup(group, options = {}) {
+ if (!(0, _utils.isObject)(group)) {
+ throw new Error("A group object is required.");
+ }
+ if (!group.id) {
+ throw new Error("A group id is required.");
+ }
+ const reqOptions = this._bucketOptions(options);
+ const data = _extends({}, options.data, group);
+ const path = (0, _endpoint2.default)("group", this.name, group.id);
+ const { permissions } = options;
+ const request = requests.updateRequest(path, { data, permissions }, reqOptions);
+ return this.client.execute(request);
+ }
+
+ /**
+ * Deletes a group from the current bucket.
+ *
+ * @param {Object|String} group The group to delete.
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Boolean} [options.safe] The safe option.
+ * @param {Number} [options.last_modified] The last_modified option.
+ * @return {Promise<Object, Error>}
+ */
+ deleteGroup(group, options = {}) {
+ const groupObj = (0, _utils.toDataBody)(group);
+ const { id, last_modified } = groupObj;
+ const reqOptions = this._bucketOptions(_extends({ last_modified }, options));
+ const path = (0, _endpoint2.default)("group", this.name, id);
+ const request = requests.deleteRequest(path, reqOptions);
+ return this.client.execute(request);
+ }
+
+ /**
+ * Retrieves the list of permissions for this bucket.
+ *
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @return {Promise<Object, Error>}
+ */
+ getPermissions(options = {}) {
+ return this.client.execute({
+ path: (0, _endpoint2.default)("bucket", this.name),
+ headers: _extends({}, this.options.headers, options.headers)
+ }).then(res => res.permissions);
+ }
+
+ /**
+ * Replaces all existing bucket permissions with the ones provided.
+ *
+ * @param {Object} permissions The permissions object.
+ * @param {Object} [options={}] The options object
+ * @param {Boolean} [options.safe] The safe option.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Object} [options.last_modified] The last_modified option.
+ * @return {Promise<Object, Error>}
+ */
+ setPermissions(permissions, options = {}) {
+ if (!(0, _utils.isObject)(permissions)) {
+ throw new Error("A permissions object is required.");
+ }
+ const path = (0, _endpoint2.default)("bucket", this.name);
+ const reqOptions = _extends({}, this._bucketOptions(options));
+ const { last_modified } = options;
+ const data = { last_modified };
+ const request = requests.updateRequest(path, { data, permissions }, reqOptions);
+ return this.client.execute(request);
+ }
+
+ /**
+ * Performs batch operations at the current bucket level.
+ *
+ * @param {Function} fn The batch operation function.
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Boolean} [options.safe] The safe option.
+ * @param {Boolean} [options.aggregate] Produces a grouped result object.
+ * @return {Promise<Object, Error>}
+ */
+ batch(fn, options = {}) {
+ return this.client.batch(fn, this._bucketOptions(options));
+ }
+};
+exports.default = Bucket;
+
+},{"./collection":5,"./endpoint":6,"./requests":9,"./utils":10}],5:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.default = undefined;
+
+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 _utils = require("./utils");
+
+var _requests = require("./requests");
+
+var requests = _interopRequireWildcard(_requests);
+
+var _endpoint = require("./endpoint");
+
+var _endpoint2 = _interopRequireDefault(_endpoint);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+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; } }
+
+/**
+ * Abstract representation of a selected collection.
+ *
+ */
+let Collection = class Collection {
+ /**
+ * Constructor.
+ *
+ * @param {KintoClient} client The client instance.
+ * @param {Bucket} bucket The bucket instance.
+ * @param {String} name The collection name.
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Boolean} [options.safe] The safe option.
+ */
+ constructor(client, bucket, name, options = {}) {
+ /**
+ * @ignore
+ */
+ this.client = client;
+ /**
+ * @ignore
+ */
+ this.bucket = bucket;
+ /**
+ * The collection name.
+ * @type {String}
+ */
+ this.name = name;
+
+ /**
+ * The default collection options object, embedding the default bucket ones.
+ * @ignore
+ * @type {Object}
+ */
+ this.options = _extends({}, this.bucket.options, options, {
+ headers: _extends({}, this.bucket.options && this.bucket.options.headers, options.headers)
+ });
+ /**
+ * @ignore
+ */
+ this._isBatch = !!options.batch;
+ }
+
+ /**
+ * Merges passed request options with default bucket and collection ones, if
+ * any.
+ *
+ * @private
+ * @param {Object} [options={}] The options to merge.
+ * @return {Object} The merged options.
+ */
+ _collOptions(options = {}) {
+ const headers = _extends({}, this.options && this.options.headers, options.headers);
+ return _extends({}, this.options, options, {
+ headers
+ });
+ }
+
+ /**
+ * Retrieves collection data.
+ *
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @return {Promise<Object, Error>}
+ */
+ getData(options = {}) {
+ const { headers } = this._collOptions(options);
+ return this.client.execute({
+ path: (0, _endpoint2.default)("collection", this.bucket.name, this.name),
+ headers
+ }).then(res => res.data);
+ }
+
+ /**
+ * Set collection data.
+ * @param {Object} data The collection data object.
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Boolean} [options.safe] The safe option.
+ * @param {Boolean} [options.patch] The patch option.
+ * @param {Number} [options.last_modified] The last_modified option.
+ * @return {Promise<Object, Error>}
+ */
+ setData(data, options = {}) {
+ if (!(0, _utils.isObject)(data)) {
+ throw new Error("A collection object is required.");
+ }
+ const reqOptions = this._collOptions(options);
+ const { permissions } = reqOptions;
+
+ const path = (0, _endpoint2.default)("collection", this.bucket.name, this.name);
+ const request = requests.updateRequest(path, { data, permissions }, reqOptions);
+ return this.client.execute(request);
+ }
+
+ /**
+ * Retrieves the list of permissions for this collection.
+ *
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @return {Promise<Object, Error>}
+ */
+ getPermissions(options = {}) {
+ const { headers } = this._collOptions(options);
+ return this.client.execute({
+ path: (0, _endpoint2.default)("collection", this.bucket.name, this.name),
+ headers
+ }).then(res => res.permissions);
+ }
+
+ /**
+ * Replaces all existing collection permissions with the ones provided.
+ *
+ * @param {Object} permissions The permissions object.
+ * @param {Object} [options={}] The options object
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Boolean} [options.safe] The safe option.
+ * @param {Number} [options.last_modified] The last_modified option.
+ * @return {Promise<Object, Error>}
+ */
+ setPermissions(permissions, options = {}) {
+ if (!(0, _utils.isObject)(permissions)) {
+ throw new Error("A permissions object is required.");
+ }
+ const reqOptions = this._collOptions(options);
+ const path = (0, _endpoint2.default)("collection", this.bucket.name, this.name);
+ const data = { last_modified: options.last_modified };
+ const request = requests.updateRequest(path, { data, permissions }, reqOptions);
+ return this.client.execute(request);
+ }
+
+ /**
+ * Creates a record in current collection.
+ *
+ * @param {Object} record The record to create.
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Boolean} [options.safe] The safe option.
+ * @return {Promise<Object, Error>}
+ */
+ createRecord(record, options = {}) {
+ const reqOptions = this._collOptions(options);
+ const { permissions } = reqOptions;
+ const path = (0, _endpoint2.default)("record", this.bucket.name, this.name, record.id);
+ const request = requests.createRequest(path, { data: record, permissions }, reqOptions);
+ return this.client.execute(request);
+ }
+
+ /**
+ * Updates a record in current collection.
+ *
+ * @param {Object} record The record to update.
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Boolean} [options.safe] The safe option.
+ * @param {Number} [options.last_modified] The last_modified option.
+ * @return {Promise<Object, Error>}
+ */
+ updateRecord(record, options = {}) {
+ if (!(0, _utils.isObject)(record)) {
+ throw new Error("A record object is required.");
+ }
+ if (!record.id) {
+ throw new Error("A record id is required.");
+ }
+ const reqOptions = this._collOptions(options);
+ const { permissions } = reqOptions;
+ const path = (0, _endpoint2.default)("record", this.bucket.name, this.name, record.id);
+ const request = requests.updateRequest(path, { data: record, permissions }, reqOptions);
+ return this.client.execute(request);
+ }
+
+ /**
+ * Deletes a record from the current collection.
+ *
+ * @param {Object|String} record The record to delete.
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Boolean} [options.safe] The safe option.
+ * @param {Number} [options.last_modified] The last_modified option.
+ * @return {Promise<Object, Error>}
+ */
+ deleteRecord(record, options = {}) {
+ const recordObj = (0, _utils.toDataBody)(record);
+ if (!recordObj.id) {
+ throw new Error("A record id is required.");
+ }
+ const { id, last_modified } = recordObj;
+ const reqOptions = this._collOptions(_extends({ last_modified }, options));
+ const path = (0, _endpoint2.default)("record", this.bucket.name, this.name, id);
+ const request = requests.deleteRequest(path, reqOptions);
+ return this.client.execute(request);
+ }
+
+ /**
+ * Retrieves a record from the current collection.
+ *
+ * @param {String} id The record id to retrieve.
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @return {Promise<Object, Error>}
+ */
+ getRecord(id, options = {}) {
+ return this.client.execute(_extends({
+ path: (0, _endpoint2.default)("record", this.bucket.name, this.name, id)
+ }, this._collOptions(options)));
+ }
+
+ /**
+ * Lists records from the current collection.
+ *
+ * Sorting is done by passing a `sort` string option:
+ *
+ * - The field to order the results by, prefixed with `-` for descending.
+ * Default: `-last_modified`.
+ *
+ * @see http://kinto.readthedocs.io/en/stable/core/api/resource.html#sorting
+ *
+ * Filtering is done by passing a `filters` option object:
+ *
+ * - `{fieldname: "value"}`
+ * - `{min_fieldname: 4000}`
+ * - `{in_fieldname: "1,2,3"}`
+ * - `{not_fieldname: 0}`
+ * - `{exclude_fieldname: "0,1"}`
+ *
+ * @see http://kinto.readthedocs.io/en/stable/core/api/resource.html#filtering
+ *
+ * Paginating is done by passing a `limit` option, then calling the `next()`
+ * method from the resolved result object to fetch the next page, if any.
+ *
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Object} [options.filters=[]] The filters object.
+ * @param {String} [options.sort="-last_modified"] The sort field.
+ * @param {String} [options.limit=null] The limit field.
+ * @param {String} [options.pages=1] The number of result pages to aggregate.
+ * @param {Number} [options.since=null] Only retrieve records modified since the provided timestamp.
+ * @return {Promise<Object, Error>}
+ */
+ listRecords(options = {}) {
+ const { http } = this.client;
+ const { sort, filters, limit, pages, since } = _extends({
+ sort: "-last_modified"
+ }, options);
+ // Safety/Consistency check on ETag value.
+ if (since && typeof since !== "string") {
+ throw new Error(`Invalid value for since (${ since }), should be ETag value.`);
+ }
+ const collHeaders = this.options.headers;
+ const path = (0, _endpoint2.default)("record", this.bucket.name, this.name);
+ const querystring = (0, _utils.qsify)(_extends({}, filters, {
+ _sort: sort,
+ _limit: limit,
+ _since: since
+ }));
+ let results = [],
+ current = 0;
+
+ const next = function (nextPage) {
+ if (!nextPage) {
+ throw new Error("Pagination exhausted.");
+ }
+ return processNextPage(nextPage);
+ };
+
+ const processNextPage = nextPage => {
+ return http.request(nextPage, { headers: collHeaders }).then(handleResponse);
+ };
+
+ const pageResults = (results, nextPage, etag) => {
+ // ETag string is supposed to be opaque and stored «as-is».
+ // ETag header values are quoted (because of * and W/"foo").
+ return {
+ last_modified: etag ? etag.replace(/"/g, "") : etag,
+ data: results,
+ next: next.bind(null, nextPage)
+ };
+ };
+
+ const handleResponse = ({ headers, json }) => {
+ const nextPage = headers.get("Next-Page");
+ const etag = headers.get("ETag");
+ if (!pages) {
+ return pageResults(json.data, nextPage, etag);
+ }
+ // Aggregate new results with previous ones
+ results = results.concat(json.data);
+ current += 1;
+ if (current >= pages || !nextPage) {
+ // Pagination exhausted
+ return pageResults(results, nextPage, etag);
+ }
+ // Follow next page
+ return processNextPage(nextPage);
+ };
+
+ return this.client.execute(_extends({
+ path: path + "?" + querystring
+ }, this._collOptions(options)), { raw: true }).then(handleResponse);
+ }
+
+ /**
+ * Performs batch operations at the current collection level.
+ *
+ * @param {Function} fn The batch operation function.
+ * @param {Object} [options={}] The options object.
+ * @param {Object} [options.headers] The headers object option.
+ * @param {Boolean} [options.safe] The safe option.
+ * @param {Boolean} [options.aggregate] Produces a grouped result object.
+ * @return {Promise<Object, Error>}
+ */
+ batch(fn, options = {}) {
+ const reqOptions = this._collOptions(options);
+ return this.client.batch(fn, _extends({}, reqOptions, {
+ bucket: this.bucket.name,
+ collection: this.name
+ }));
+ }
+};
+exports.default = Collection;
+
+},{"./endpoint":6,"./requests":9,"./utils":10}],6:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.default = endpoint;
+/**
+ * Endpoints templates.
+ * @type {Object}
+ */
+const ENDPOINTS = {
+ root: () => "/",
+ batch: () => "/batch",
+ bucket: bucket => "/buckets" + (bucket ? `/${ bucket }` : ""),
+ collection: (bucket, coll) => `${ ENDPOINTS.bucket(bucket) }/collections` + (coll ? `/${ coll }` : ""),
+ group: (bucket, group) => `${ ENDPOINTS.bucket(bucket) }/groups` + (group ? `/${ group }` : ""),
+ record: (bucket, coll, id) => `${ ENDPOINTS.collection(bucket, coll) }/records` + (id ? `/${ id }` : "")
+};
+
+/**
+ * Retrieves a server enpoint by its name.
+ *
+ * @private
+ * @param {String} name The endpoint name.
+ * @param {...string} args The endpoint parameters.
+ * @return {String}
+ */
+function endpoint(name, ...args) {
+ return ENDPOINTS[name](...args);
+}
+
+},{}],7:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+/**
+ * Kinto server error code descriptors.
+ * @type {Object}
+ */
+exports.default = {
+ 104: "Missing Authorization Token",
+ 105: "Invalid Authorization Token",
+ 106: "Request body was not valid JSON",
+ 107: "Invalid request parameter",
+ 108: "Missing request parameter",
+ 109: "Invalid posted data",
+ 110: "Invalid Token / id",
+ 111: "Missing Token / id",
+ 112: "Content-Length header was not provided",
+ 113: "Request body too large",
+ 114: "Resource was modified meanwhile",
+ 115: "Method not allowed on this end point (hint: server may be readonly)",
+ 116: "Requested version not available on this server",
+ 117: "Client has sent too many requests",
+ 121: "Resource access is forbidden for this user",
+ 122: "Another resource violates constraint",
+ 201: "Service Temporary unavailable due to high load",
+ 202: "Service deprecated",
+ 999: "Internal Server Error"
+};
+
+},{}],8:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.default = undefined;
+
+var _errors = require("./errors");
+
+var _errors2 = _interopRequireDefault(_errors);
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+/**
+ * Enhanced HTTP client for the Kinto protocol.
+ * @private
+ */
+let HTTP = class HTTP {
+ /**
+ * Default HTTP request headers applied to each outgoing request.
+ *
+ * @type {Object}
+ */
+ static get DEFAULT_REQUEST_HEADERS() {
+ return {
+ "Accept": "application/json",
+ "Content-Type": "application/json"
+ };
+ }
+
+ /**
+ * Default options.
+ *
+ * @type {Object}
+ */
+ static get defaultOptions() {
+ return { timeout: 5000, requestMode: "cors" };
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param {EventEmitter} events The event handler.
+ * @param {Object} [options={}} The options object.
+ * @param {Number} [options.timeout=5000] The request timeout in ms (default: `5000`).
+ * @param {String} [options.requestMode="cors"] The HTTP request mode (default: `"cors"`).
+ */
+ constructor(events, options = {}) {
+ // public properties
+ /**
+ * The event emitter instance.
+ * @type {EventEmitter}
+ */
+ if (!events) {
+ throw new Error("No events handler provided");
+ }
+ this.events = events;
+
+ /**
+ * The request mode.
+ * @see https://fetch.spec.whatwg.org/#requestmode
+ * @type {String}
+ */
+ this.requestMode = options.requestMode || HTTP.defaultOptions.requestMode;
+
+ /**
+ * The request timeout.
+ * @type {Number}
+ */
+ this.timeout = options.timeout || HTTP.defaultOptions.timeout;
+ }
+
+ /**
+ * Performs an HTTP request to the Kinto server.
+ *
+ * Resolves with an objet containing the following HTTP response properties:
+ * - `{Number} status` The HTTP status code.
+ * - `{Object} json` The JSON response body.
+ * - `{Headers} headers` The response headers object; see the ES6 fetch() spec.
+ *
+ * @param {String} url The URL.
+ * @param {Object} [options={}] The fetch() options object.
+ * @param {Object} [options.headers] The request headers object (default: {})
+ * @return {Promise}
+ */
+ request(url, options = { headers: {} }) {
+ let response, status, statusText, headers, hasTimedout;
+ // Ensure default request headers are always set
+ options.headers = Object.assign({}, HTTP.DEFAULT_REQUEST_HEADERS, options.headers);
+ options.mode = this.requestMode;
+ return new Promise((resolve, reject) => {
+ const _timeoutId = setTimeout(() => {
+ hasTimedout = true;
+ reject(new Error("Request timeout."));
+ }, this.timeout);
+ fetch(url, options).then(res => {
+ if (!hasTimedout) {
+ clearTimeout(_timeoutId);
+ resolve(res);
+ }
+ }).catch(err => {
+ if (!hasTimedout) {
+ clearTimeout(_timeoutId);
+ reject(err);
+ }
+ });
+ }).then(res => {
+ response = res;
+ headers = res.headers;
+ status = res.status;
+ statusText = res.statusText;
+ this._checkForDeprecationHeader(headers);
+ this._checkForBackoffHeader(status, headers);
+ this._checkForRetryAfterHeader(status, headers);
+ return res.text();
+ })
+ // Check if we have a body; if so parse it as JSON.
+ .then(text => {
+ if (text.length === 0) {
+ return null;
+ }
+ // Note: we can't consume the response body twice.
+ return JSON.parse(text);
+ }).catch(err => {
+ const error = new Error(`HTTP ${ status || 0 }; ${ err }`);
+ error.response = response;
+ error.stack = err.stack;
+ throw error;
+ }).then(json => {
+ if (json && status >= 400) {
+ let message = `HTTP ${ status } ${ json.error || "" }: `;
+ if (json.errno && json.errno in _errors2.default) {
+ const errnoMsg = _errors2.default[json.errno];
+ message += errnoMsg;
+ if (json.message && json.message !== errnoMsg) {
+ message += ` (${ json.message })`;
+ }
+ } else {
+ message += statusText || "";
+ }
+ const error = new Error(message.trim());
+ error.response = response;
+ error.data = json;
+ throw error;
+ }
+ return { status, json, headers };
+ });
+ }
+
+ _checkForDeprecationHeader(headers) {
+ const alertHeader = headers.get("Alert");
+ if (!alertHeader) {
+ return;
+ }
+ let alert;
+ try {
+ alert = JSON.parse(alertHeader);
+ } catch (err) {
+ console.warn("Unable to parse Alert header message", alertHeader);
+ return;
+ }
+ console.warn(alert.message, alert.url);
+ this.events.emit("deprecated", alert);
+ }
+
+ _checkForBackoffHeader(status, headers) {
+ let backoffMs;
+ const backoffSeconds = parseInt(headers.get("Backoff"), 10);
+ if (backoffSeconds > 0) {
+ backoffMs = new Date().getTime() + backoffSeconds * 1000;
+ } else {
+ backoffMs = 0;
+ }
+ this.events.emit("backoff", backoffMs);
+ }
+
+ _checkForRetryAfterHeader(status, headers) {
+ let retryAfter = headers.get("Retry-After");
+ if (!retryAfter) {
+ return;
+ }
+ retryAfter = new Date().getTime() + parseInt(retryAfter, 10) * 1000;
+ this.events.emit("retry-after", retryAfter);
+ }
+};
+exports.default = HTTP;
+
+},{"./errors":7}],9:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+
+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; };
+
+exports.createRequest = createRequest;
+exports.updateRequest = updateRequest;
+exports.deleteRequest = deleteRequest;
+
+var _utils = require("./utils");
+
+const requestDefaults = {
+ safe: false,
+ // check if we should set default content type here
+ headers: {},
+ permissions: undefined,
+ data: undefined,
+ patch: false
+};
+
+/**
+ * @private
+ */
+function safeHeader(safe, last_modified) {
+ if (!safe) {
+ return {};
+ }
+ if (last_modified) {
+ return { "If-Match": `"${ last_modified }"` };
+ }
+ return { "If-None-Match": "*" };
+}
+
+/**
+ * @private
+ */
+function createRequest(path, { data, permissions }, options = {}) {
+ const { headers, safe } = _extends({}, requestDefaults, options);
+ return {
+ method: data && data.id ? "PUT" : "POST",
+ path,
+ headers: _extends({}, headers, safeHeader(safe)),
+ body: {
+ data,
+ permissions
+ }
+ };
+}
+
+/**
+ * @private
+ */
+function updateRequest(path, { data, permissions }, options = {}) {
+ const {
+ headers,
+ safe,
+ patch
+ } = _extends({}, requestDefaults, options);
+ const { last_modified } = _extends({}, data, options);
+
+ if (Object.keys((0, _utils.omit)(data, "id", "last_modified")).length === 0) {
+ data = undefined;
+ }
+
+ return {
+ method: patch ? "PATCH" : "PUT",
+ path,
+ headers: _extends({}, headers, safeHeader(safe, last_modified)),
+ body: {
+ data,
+ permissions
+ }
+ };
+}
+
+/**
+ * @private
+ */
+function deleteRequest(path, options = {}) {
+ const { headers, safe, last_modified } = _extends({}, requestDefaults, options);
+ if (safe && !last_modified) {
+ throw new Error("Safe concurrency check requires a last_modified value.");
+ }
+ return {
+ method: "DELETE",
+ path,
+ headers: _extends({}, headers, safeHeader(safe, last_modified))
+ };
+}
+
+},{"./utils":10}],10:[function(require,module,exports){
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+exports.partition = partition;
+exports.pMap = pMap;
+exports.omit = omit;
+exports.toDataBody = toDataBody;
+exports.qsify = qsify;
+exports.checkVersion = checkVersion;
+exports.support = support;
+exports.capable = capable;
+exports.nobatch = nobatch;
+exports.isObject = isObject;
+/**
+ * Chunks an array into n pieces.
+ *
+ * @private
+ * @param {Array} array
+ * @param {Number} n
+ * @return {Array}
+ */
+function partition(array, n) {
+ if (n <= 0) {
+ return array;
+ }
+ return array.reduce((acc, x, i) => {
+ if (i === 0 || i % n === 0) {
+ acc.push([x]);
+ } else {
+ acc[acc.length - 1].push(x);
+ }
+ return acc;
+ }, []);
+}
+
+/**
+ * Maps a list to promises using the provided mapping function, executes them
+ * sequentially then returns a Promise resolving with ordered results obtained.
+ * Think of this as a sequential Promise.all.
+ *
+ * @private
+ * @param {Array} list The list to map.
+ * @param {Function} fn The mapping function.
+ * @return {Promise}
+ */
+function pMap(list, fn) {
+ let results = [];
+ return list.reduce((promise, entry) => {
+ return promise.then(() => {
+ return Promise.resolve(fn(entry)).then(result => results = results.concat(result));
+ });
+ }, Promise.resolve()).then(() => results);
+}
+
+/**
+ * Takes an object and returns a copy of it with the provided keys omitted.
+ *
+ * @private
+ * @param {Object} obj The source object.
+ * @param {...String} keys The keys to omit.
+ * @return {Object}
+ */
+function omit(obj, ...keys) {
+ return Object.keys(obj).reduce((acc, key) => {
+ if (keys.indexOf(key) === -1) {
+ acc[key] = obj[key];
+ }
+ return acc;
+ }, {});
+}
+
+/**
+ * Always returns a resource data object from the provided argument.
+ *
+ * @private
+ * @param {Object|String} resource
+ * @return {Object}
+ */
+function toDataBody(resource) {
+ if (isObject(resource)) {
+ return resource;
+ }
+ if (typeof resource === "string") {
+ return { id: resource };
+ }
+ throw new Error("Invalid argument.");
+}
+
+/**
+ * Transforms an object into an URL query string, stripping out any undefined
+ * values.
+ *
+ * @param {Object} obj
+ * @return {String}
+ */
+function qsify(obj) {
+ const sep = "&";
+ const encode = v => encodeURIComponent(typeof v === "boolean" ? String(v) : v);
+ const stripUndefined = o => JSON.parse(JSON.stringify(o));
+ const stripped = stripUndefined(obj);
+ return Object.keys(stripped).map(k => {
+ const ks = encode(k) + "=";
+ if (Array.isArray(stripped[k])) {
+ return stripped[k].map(v => ks + encode(v)).join(sep);
+ } else {
+ return ks + encode(stripped[k]);
+ }
+ }).join(sep);
+}
+
+/**
+ * Checks if a version is within the provided range.
+ *
+ * @param {String} version The version to check.
+ * @param {String} minVersion The minimum supported version (inclusive).
+ * @param {String} maxVersion The minimum supported version (exclusive).
+ * @throws {Error} If the version is outside of the provided range.
+ */
+function checkVersion(version, minVersion, maxVersion) {
+ const extract = str => str.split(".").map(x => parseInt(x, 10));
+ const [verMajor, verMinor] = extract(version);
+ const [minMajor, minMinor] = extract(minVersion);
+ const [maxMajor, maxMinor] = extract(maxVersion);
+ const checks = [verMajor < minMajor, verMajor === minMajor && verMinor < minMinor, verMajor > maxMajor, verMajor === maxMajor && verMinor >= maxMinor];
+ if (checks.some(x => x)) {
+ throw new Error(`Version ${ version } doesn't satisfy ` + `${ minVersion } <= x < ${ maxVersion }`);
+ }
+}
+
+/**
+ * Generates a decorator function ensuring a version check is performed against
+ * the provided requirements before executing it.
+ *
+ * @param {String} min The required min version (inclusive).
+ * @param {String} max The required max version (inclusive).
+ * @return {Function}
+ */
+function support(min, max) {
+ return function (target, key, descriptor) {
+ const fn = descriptor.value;
+ return {
+ configurable: true,
+ get() {
+ const wrappedMethod = (...args) => {
+ // "this" is the current instance which its method is decorated.
+ const client = "client" in this ? this.client : this;
+ return client.fetchHTTPApiVersion().then(version => checkVersion(version, min, max)).then(Promise.resolve(fn.apply(this, args)));
+ };
+ Object.defineProperty(this, key, {
+ value: wrappedMethod,
+ configurable: true,
+ writable: true
+ });
+ return wrappedMethod;
+ }
+ };
+ };
+}
+
+/**
+ * Generates a decorator function ensuring that the specified capabilities are
+ * available on the server before executing it.
+ *
+ * @param {Array<String>} capabilities The required capabilities.
+ * @return {Function}
+ */
+function capable(capabilities) {
+ return function (target, key, descriptor) {
+ const fn = descriptor.value;
+ return {
+ configurable: true,
+ get() {
+ const wrappedMethod = (...args) => {
+ // "this" is the current instance which its method is decorated.
+ const client = "client" in this ? this.client : this;
+ return client.fetchServerCapabilities().then(available => {
+ const missing = capabilities.filter(c => available.indexOf(c) < 0);
+ if (missing.length > 0) {
+ throw new Error(`Required capabilities ${ missing.join(", ") } ` + "not present on server");
+ }
+ }).then(Promise.resolve(fn.apply(this, args)));
+ };
+ Object.defineProperty(this, key, {
+ value: wrappedMethod,
+ configurable: true,
+ writable: true
+ });
+ return wrappedMethod;
+ }
+ };
+ };
+}
+
+/**
+ * Generates a decorator function ensuring an operation is not performed from
+ * within a batch request.
+ *
+ * @param {String} message The error message to throw.
+ * @return {Function}
+ */
+function nobatch(message) {
+ return function (target, key, descriptor) {
+ const fn = descriptor.value;
+ return {
+ configurable: true,
+ get() {
+ const wrappedMethod = (...args) => {
+ // "this" is the current instance which its method is decorated.
+ if (this._isBatch) {
+ throw new Error(message);
+ }
+ return fn.apply(this, args);
+ };
+ Object.defineProperty(this, key, {
+ value: wrappedMethod,
+ configurable: true,
+ writable: true
+ });
+ return wrappedMethod;
+ }
+ };
+ };
+}
+
+/**
+ * Returns true if the specified value is an object (i.e. not an array nor null).
+ * @param {Object} thing The value to inspect.
+ * @return {bool}
+ */
+function isObject(thing) {
+ return typeof thing === "object" && thing !== null && !Array.isArray(thing);
+}
+
+},{}]},{},[1])(1)
+}); \ No newline at end of file