diff --git a/index.html b/index.html
index 9e5cdb8022dbfba22daa9d5d2198eda9584b8165..f2fc1d3fdf35a39e6c4025e997896178d7729254 100644
--- a/index.html
+++ b/index.html
@@ -1 +1 @@
-<!DOCTYPE html><html lang="{{.Locale}}"><head><meta charset="utf-8"><title>Ecolyo</title><link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32"><link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16"><!-- PWA Manifest --><link rel="manifest" href="/manifest.json" crossorigin="use-credentials"><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#297EF2"><meta name="viewport" content="width=device-width,height=device-height,initial-scale=1,viewport-fit=cover"><!-- PWA Chrome --><link rel="icon" sizes="192x192" href="/android-chrome-192x192.png"><link rel="icon" sizes="512x512" href="/android-chrome-512x512.png"><!-- PWA iOS --><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="apple-touch-startup-image" href="/apple-touch-icon.png"><meta name="apple-mobile-web-app-title" content="Ecolyo"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><!-- PWA Colors --><meta name="theme-color" content="#343641"><meta name="background-color" content="#121212">{{.ThemeCSS}} {{.CozyBar}}<script src="//{{.Domain}}/assets/js/piwik.js"></script></head><body><div role="application" class="application" data-cozy="{{.CozyData}}"><script src="vendors/ecolyo.0f66f88c2aa7f446186b.js"></script><script src="app/ecolyo.554a8422165fe8f24237.js"></script></div></body></html>
\ No newline at end of file
+<!DOCTYPE html><html lang="{{.Locale}}"><head><meta charset="utf-8"><title>Ecolyo</title><link rel="icon" type="image/png" href="/favicon-32x32.png" sizes="32x32"><link rel="icon" type="image/png" href="/favicon-16x16.png" sizes="16x16"><!-- PWA Manifest --><link rel="manifest" href="/manifest.json" crossorigin="use-credentials"><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#297EF2"><meta name="viewport" content="width=device-width,height=device-height,initial-scale=1,viewport-fit=cover"><!-- PWA Chrome --><link rel="icon" sizes="192x192" href="/android-chrome-192x192.png"><link rel="icon" sizes="512x512" href="/android-chrome-512x512.png"><!-- PWA iOS --><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"><link rel="apple-touch-startup-image" href="/apple-touch-icon.png"><meta name="apple-mobile-web-app-title" content="Ecolyo"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><!-- PWA Colors --><meta name="theme-color" content="#343641"><meta name="background-color" content="#121212">{{.ThemeCSS}} {{.CozyBar}}<script src="//{{.Domain}}/assets/js/piwik.js"></script></head><body><div role="application" class="application" data-cozy="{{.CozyData}}"><script src="vendors/ecolyo.84c5768e0b0aa3838025.js"></script><script src="app/ecolyo.554a8422165fe8f24237.js"></script></div></body></html>
\ No newline at end of file
diff --git a/public/ecolyo.297fa83f17e3216f8d1a.js b/public/ecolyo.4555ae0963be5bd89ab7.js
similarity index 99%
rename from public/ecolyo.297fa83f17e3216f8d1a.js
rename to public/ecolyo.4555ae0963be5bd89ab7.js
index 8cd569273bc7e9d56b822a3c0829f7d3e5313d42..2c8d38565f0bedb5f588c6b5609f7dce069d25d8 100644
--- a/public/ecolyo.297fa83f17e3216f8d1a.js
+++ b/public/ecolyo.4555ae0963be5bd89ab7.js
@@ -1554,19 +1554,22 @@ var requireDoubleUnsubscriptions = true;
 exports.requireDoubleUnsubscriptions = requireDoubleUnsubscriptions;
 
 var onDoubleSubscriptions = function onDoubleSubscriptions(subscription) {
-  _logger.default.warn('Double subscription for ', subscription);
+  var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
+      _ref$logger = _ref.logger,
+      logger = _ref$logger === void 0 ? _logger.default : _ref$logger;
 
-  if (allowDoubleSubscriptions) {
-    _logger.default.info('The handler may be called twice for the same event!');
+  logger.warn('Double subscription for ', subscription);
 
-    _logger.default.info('Remember to call one `unsubscribe` for each `subscribe`');
+  if (allowDoubleSubscriptions) {
+    logger.info('The handler may be called twice for the same event!');
+    logger.info('Remember to call one `unsubscribe` for each `subscribe`');
   } else {
-    _logger.default.info('The handler will only be called once');
+    logger.info('The handler will only be called once');
 
     if (requireDoubleUnsubscriptions) {
-      _logger.default.info('Remember to call one `unsubscribe` for each `subscribe`');
+      logger.info('Remember to call one `unsubscribe` for each `subscribe`');
     } else {
-      _logger.default.info('`unsubscribe` will remove all similar subscriptions');
+      logger.info('`unsubscribe` will remove all similar subscriptions');
     }
   }
 };
@@ -2655,154 +2658,6 @@ function startOfWeek (dirtyDate, dirtyOptions) {
 module.exports = startOfWeek
 
 
-/***/ }),
-
-/***/ "/K1E":
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-Object.defineProperty(exports, "checkApp", {
-  enumerable: true,
-  get: function get() {
-    return _apps.checkApp;
-  }
-});
-Object.defineProperty(exports, "getDeviceName", {
-  enumerable: true,
-  get: function get() {
-    return _device.getDeviceName;
-  }
-});
-Object.defineProperty(exports, "getFlagshipMetadata", {
-  enumerable: true,
-  get: function get() {
-    return _flagship.getFlagshipMetadata;
-  }
-});
-Object.defineProperty(exports, "getPlatform", {
-  enumerable: true,
-  get: function get() {
-    return _platform.getPlatform;
-  }
-});
-Object.defineProperty(exports, "hasDevicePlugin", {
-  enumerable: true,
-  get: function get() {
-    return _plugins.hasDevicePlugin;
-  }
-});
-Object.defineProperty(exports, "hasInAppBrowserPlugin", {
-  enumerable: true,
-  get: function get() {
-    return _plugins.hasInAppBrowserPlugin;
-  }
-});
-Object.defineProperty(exports, "hasNetworkInformationPlugin", {
-  enumerable: true,
-  get: function get() {
-    return _plugins.hasNetworkInformationPlugin;
-  }
-});
-Object.defineProperty(exports, "hasSafariPlugin", {
-  enumerable: true,
-  get: function get() {
-    return _plugins.hasSafariPlugin;
-  }
-});
-Object.defineProperty(exports, "isAndroid", {
-  enumerable: true,
-  get: function get() {
-    return _platform.isAndroid;
-  }
-});
-Object.defineProperty(exports, "isAndroidApp", {
-  enumerable: true,
-  get: function get() {
-    return _platform.isAndroidApp;
-  }
-});
-Object.defineProperty(exports, "isCordova", {
-  enumerable: true,
-  get: function get() {
-    return _cordova.isCordova;
-  }
-});
-Object.defineProperty(exports, "isFlagshipApp", {
-  enumerable: true,
-  get: function get() {
-    return _flagship.isFlagshipApp;
-  }
-});
-Object.defineProperty(exports, "isIOS", {
-  enumerable: true,
-  get: function get() {
-    return _platform.isIOS;
-  }
-});
-Object.defineProperty(exports, "isIOSApp", {
-  enumerable: true,
-  get: function get() {
-    return _platform.isIOSApp;
-  }
-});
-Object.defineProperty(exports, "isMobile", {
-  enumerable: true,
-  get: function get() {
-    return _platform.isMobile;
-  }
-});
-Object.defineProperty(exports, "isMobileApp", {
-  enumerable: true,
-  get: function get() {
-    return _platform.isMobileApp;
-  }
-});
-Object.defineProperty(exports, "isWebApp", {
-  enumerable: true,
-  get: function get() {
-    return _platform.isWebApp;
-  }
-});
-Object.defineProperty(exports, "nativeLinkOpen", {
-  enumerable: true,
-  get: function get() {
-    return _link.nativeLinkOpen;
-  }
-});
-Object.defineProperty(exports, "openDeeplinkOrRedirect", {
-  enumerable: true,
-  get: function get() {
-    return _deeplink.openDeeplinkOrRedirect;
-  }
-});
-Object.defineProperty(exports, "startApp", {
-  enumerable: true,
-  get: function get() {
-    return _apps.startApp;
-  }
-});
-
-var _platform = __webpack_require__("AzAX");
-
-var _device = __webpack_require__("m6Tf");
-
-var _apps = __webpack_require__("yL+W");
-
-var _plugins = __webpack_require__("QJIl");
-
-var _cordova = __webpack_require__("dlno");
-
-var _link = __webpack_require__("tDKi");
-
-var _deeplink = __webpack_require__("AIES");
-
-var _flagship = __webpack_require__("lNPj");
-
 /***/ }),
 
 /***/ "/Mg5":
@@ -18766,6 +18621,11 @@ var _createClass2 = _interopRequireDefault(__webpack_require__("Zvb3"));
 
 var _CozyRealtime = _interopRequireDefault(__webpack_require__("PdKc"));
 
+/**
+ * A cozy-client instance.
+ * @typedef {import("cozy-client/dist/index").CozyClient} CozyClient
+ */
+
 /**
  * Realtime plugin for cozy-client
  *
@@ -18780,10 +18640,16 @@ var RealtimePlugin = /*#__PURE__*/function () {
    *
    * @constructor
    * @param {CozyClient} client A cozy-client instance
+   * @param {object} options
+   * @param {Function} [options.createWebSocket] The function used to create WebSocket instances
+   * @param {object} [options.logger] A custom logger for CozyRealtime
    */
   function RealtimePlugin(client) {
+    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
     (0, _classCallCheck2.default)(this, RealtimePlugin);
     this.client = client;
+    this.createWebSocket = options.createWebSocket;
+    this.logger = options.logger;
     this.realtime = null;
     this.handleLogin = this.handleLogin.bind(this);
     this.handleLogout = this.handleLogout.bind(this);
@@ -18796,7 +18662,9 @@ var RealtimePlugin = /*#__PURE__*/function () {
     key: "handleLogin",
     value: function handleLogin() {
       this.realtime = new _CozyRealtime.default({
-        client: this.client
+        client: this.client,
+        createWebSocket: this.createWebSocket,
+        logger: this.logger
       });
       this.client.emit('plugin:realtime:login');
     }
@@ -42996,129 +42864,6 @@ exports.default = _default;
 
 /***/ }),
 
-/***/ "AIES":
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.openDeeplinkOrRedirect = void 0;
-
-/**
- * This file is used to open the native app from a webapp
- * if this native app is installed
- *
- * From a webapp, we don't have any clue if a native app is installed.
- * The only way to know that, is to try to open the custom link
- * (aka cozydrive://) and if nothing happens (no blur) we redirect to
- * the callback
- *
- * Firefox tries to open custom link, so we need to create an iframe
- * to detect if this is supported or not
- */
-var _createHiddenIframe = function _createHiddenIframe(target, uri, randomId) {
-  var iframe = document.createElement('iframe');
-  iframe.src = uri;
-  iframe.id = "hiddenIframe_".concat(randomId);
-  iframe.style.display = 'none';
-  target.appendChild(iframe);
-  return iframe;
-};
-
-var openUriWithHiddenFrame = function openUriWithHiddenFrame(uri, failCb) {
-  var randomId = Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);
-  window.addEventListener('blur', onBlur);
-
-  var iframe = _createHiddenIframe(document.body, 'about:blank', randomId);
-
-  var timeout = setTimeout(function () {
-    failCb();
-    window.removeEventListener('blur', onBlur);
-    iframe.parentElement.removeChild(iframe);
-  }, 500);
-
-  function onBlur() {
-    clearTimeout(timeout);
-    window.removeEventListener('blur', onBlur);
-    iframe.parentElement.removeChild(iframe);
-  }
-
-  iframe.contentWindow.location.href = uri;
-};
-
-var openUriWithTimeoutHack = function openUriWithTimeoutHack(uri, failCb) {
-  var timeout = setTimeout(function () {
-    failCb();
-    target.removeEventListener('blur', onBlur);
-  }, 500); // handle page running in an iframe (blur must be registered with top level window)
-
-  var target = window;
-
-  while (target != target.parent) {
-    target = target.parent;
-  }
-
-  target.addEventListener('blur', onBlur);
-
-  function onBlur() {
-    clearTimeout(timeout);
-    target.removeEventListener('blur', onBlur);
-  } // Why is an uri assigned to location object?
-
-
-  window.location = uri;
-};
-
-var openUriWithMsLaunchUri = function openUriWithMsLaunchUri(uri, failCb) {
-  navigator.msLaunchUri(uri, undefined, failCb);
-};
-
-var checkBrowser = function checkBrowser() {
-  var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
-  var ua = navigator.userAgent.toLowerCase();
-  var isSafari = ua.includes('safari') && !ua.includes('chrome') || Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
-  var isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
-  var isIOS122 = isIOS && (ua.includes('os 12_2') || ua.includes('os 12_3'));
-  return {
-    isOpera: isOpera,
-    isFirefox: typeof window.InstallTrigger !== 'undefined',
-    isSafari: isSafari,
-    isChrome: !!window.chrome && !isOpera,
-    isIOS122: isIOS122,
-    isIOS: isIOS
-  };
-};
-/**
- *
- * @param {String} deeplink (cozydrive://)
- * @param {String} failCb (http://drive.cozy.ios)
- */
-
-
-var openDeeplinkOrRedirect = function openDeeplinkOrRedirect(deeplink, failCb) {
-  if (navigator.msLaunchUri) {
-    // for IE and Edge in Win 8 and Win 10
-    openUriWithMsLaunchUri(deeplink, failCb);
-  } else {
-    var browser = checkBrowser();
-
-    if (browser.isChrome || browser.isIOS && !browser.isIOS122) {
-      openUriWithTimeoutHack(deeplink, failCb);
-    } else if (browser.isSafari && !browser.isIOS122 || browser.isFirefox) {
-      openUriWithHiddenFrame(deeplink, failCb);
-    } else {
-      failCb();
-    }
-  }
-};
-
-exports.openDeeplinkOrRedirect = openDeeplinkOrRedirect;
-
-/***/ }),
-
 /***/ "AJH6":
 /***/ (function(module, exports) {
 
@@ -45544,80 +45289,6 @@ module.exports = function fill(value /* , start = 0, end = @length */) {
 };
 
 
-/***/ }),
-
-/***/ "AzAX":
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.isWebApp = exports.isMobileApp = exports.isMobile = exports.isIOSApp = exports.isIOS = exports.isAndroidApp = exports.isAndroid = exports.getPlatform = void 0;
-
-var _cordova = __webpack_require__("dlno");
-
-var ANDROID_PLATFORM = 'android';
-var IOS_PLATFORM = 'ios';
-var WEB_PLATFORM = 'web';
-
-var getPlatform = function getPlatform() {
-  return (0, _cordova.isCordova)() ? window.cordova.platformId : WEB_PLATFORM;
-};
-
-exports.getPlatform = getPlatform;
-
-var isPlatform = function isPlatform(platform) {
-  return getPlatform() === platform;
-};
-
-var isIOSApp = function isIOSApp() {
-  return isPlatform(IOS_PLATFORM);
-};
-
-exports.isIOSApp = isIOSApp;
-
-var isAndroidApp = function isAndroidApp() {
-  return isPlatform(ANDROID_PLATFORM);
-};
-
-exports.isAndroidApp = isAndroidApp;
-
-var isWebApp = function isWebApp() {
-  return isPlatform(WEB_PLATFORM);
-};
-
-exports.isWebApp = isWebApp;
-
-var isMobileApp = function isMobileApp() {
-  return (0, _cordova.isCordova)();
-}; // return if is on an Android Device (native or browser)
-
-
-exports.isMobileApp = isMobileApp;
-
-var isAndroid = function isAndroid() {
-  return window.navigator.userAgent && window.navigator.userAgent.indexOf('Android') >= 0;
-}; // return if is on an iOS Device (native or browser)
-
-
-exports.isAndroid = isAndroid;
-
-var isIOS = function isIOS() {
-  return window.navigator.userAgent && /iPad|iPhone|iPod/.test(window.navigator.userAgent);
-}; // isMobile checks if the user is on a smartphone : native app or browser
-
-
-exports.isIOS = isIOS;
-
-var isMobile = function isMobile() {
-  return isAndroid() || isIOS();
-};
-
-exports.isMobile = isMobile;
-
 /***/ }),
 
 /***/ "B/g2":
@@ -87231,6 +86902,8 @@ exports.default = void 0;
 
 var _regenerator = _interopRequireDefault(__webpack_require__("hJxD"));
 
+var _defineProperty2 = _interopRequireDefault(__webpack_require__("J58c"));
+
 var _asyncToGenerator2 = _interopRequireDefault(__webpack_require__("HZZ/"));
 
 var _classCallCheck2 = _interopRequireDefault(__webpack_require__("GeFe"));
@@ -87239,17 +86912,21 @@ var _createClass2 = _interopRequireDefault(__webpack_require__("Zvb3"));
 
 var _microee = _interopRequireDefault(__webpack_require__("GIvT"));
 
-var _cozyDeviceHelper = __webpack_require__("/K1E");
+var _cozyDeviceHelper = __webpack_require__("Kv7L");
 
-var _logger = _interopRequireDefault(__webpack_require__("o2qs"));
+var _RetryManager = _interopRequireDefault(__webpack_require__("oeo/"));
 
 var _SubscriptionList = _interopRequireDefault(__webpack_require__("q5BW"));
 
-var _RetryManager = _interopRequireDefault(__webpack_require__("oeo/"));
+var _config = __webpack_require__("+RmU");
+
+var _logger = _interopRequireDefault(__webpack_require__("o2qs"));
 
 var _utils = __webpack_require__("rO7t");
 
-var _config = __webpack_require__("+RmU");
+function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
+
+function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
 
 function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
 
@@ -87257,6 +86934,11 @@ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o =
 
 function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
 
+/**
+ * A cozy-client instance.
+ * @typedef {import("cozy-client/dist/index").CozyClient} CozyClient
+ */
+
 /**
  * Manage the realtime interactions with a cozy stack
  */
@@ -87264,19 +86946,26 @@ var CozyRealtime = /*#__PURE__*/function () {
   /**
    * @constructor
    * @param {object} options
-   * @param {CozyClient}  options.client
+   * @param {CozyClient}  options.client A cozy-client instance
+   * @param {Function} [options.createWebSocket] The function used to create WebSocket instances
+   * @param {object} [options.logger] A custom logger
    */
   function CozyRealtime(options) {
     var _this = this;
 
     (0, _classCallCheck2.default)(this, CozyRealtime);
     this.client = (0, _utils.getCozyClientFromOptions)(options);
-    this.subscriptions = new _SubscriptionList.default();
+    this.createWebSocket = options.createWebSocket || _utils.createWebSocket;
+    this.logger = options.logger || _logger.default;
+    this.subscriptions = new _SubscriptionList.default({
+      logger: options.logger
+    });
     this.retryManager = new _RetryManager.default({
       raiseErrorAfterAttempts: _config.raiseErrorAfterAttempts,
       timeBeforeSuccessful: _config.timeBeforeSuccessful,
       baseWaitAfterFirstFailure: _config.baseWaitAfterFirstFailure,
-      maxWaitBetweenRetries: _config.maxWaitBetweenRetries
+      maxWaitBetweenRetries: _config.maxWaitBetweenRetries,
+      logger: options.logger
     });
     this.retryManager.on('error', function (err) {
       return _this.emit('error', err);
@@ -87284,7 +86973,7 @@ var CozyRealtime = /*#__PURE__*/function () {
     this.bindEventHandlers();
 
     if ((0, _cozyDeviceHelper.isCordova)() && !(0, _cozyDeviceHelper.hasNetworkInformationPlugin)()) {
-      _logger.default.warn("This seems a Cordova app and cordova-plugin-network-information doesn't seem to be installed. Please install it from https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-network-information/ to support online and offline events.");
+      this.logger.warn("This seems a Cordova app and cordova-plugin-network-information doesn't seem to be installed. Please install it from https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-network-information/ to support online and offline events.");
     }
   }
   /**
@@ -87300,8 +86989,7 @@ var CozyRealtime = /*#__PURE__*/function () {
     key: "start",
     value: function start() {
       if (!this.isStarted) {
-        _logger.default.info('started');
-
+        this.logger.info('started');
         this.isStarted = true;
         this.retryManager.reset();
         this.subscribeGlobalEvents();
@@ -87322,17 +87010,15 @@ var CozyRealtime = /*#__PURE__*/function () {
     key: "createSocket",
     value: function createSocket() {
       if (this.websocket) {
-        _logger.default.error('Unexpected replacement of an existing socket, this should not happen. A `revokeWebSocket()` should have been called first inside CozyRealtime');
-
+        this.logger.error('Unexpected replacement of an existing socket, this should not happen. A `revokeWebSocket()` should have been called first inside CozyRealtime');
         this.revokeWebSocket();
       }
 
-      _logger.default.info('creating a new websocket…');
-
+      this.logger.info('creating a new websocket…');
       var url = (0, _utils.getUrl)(this.client);
 
       try {
-        this.websocket = new WebSocket(url, _utils.doctype);
+        this.websocket = this.createWebSocket(url, _utils.protocol);
         this.websocket.authenticated = false;
         this.websocket.onmessage = this.onWebSocketMessage;
         this.websocket.onerror = this.onWebSocketError;
@@ -87362,24 +87048,20 @@ var CozyRealtime = /*#__PURE__*/function () {
             switch (_context.prev = _context.next) {
               case 0:
                 _ref = _args.length > 0 && _args[0] !== undefined ? _args[0] : {}, _ref$immediate = _ref.immediate, immediate = _ref$immediate === void 0 ? false : _ref$immediate;
-
-                _logger.default.info('connecting…'); // avoid multiple concurrent calls to connect, keeps the first one
-
+                this.logger.info('connecting…'); // avoid multiple concurrent calls to connect, keeps the first one
 
                 if (!this.waitingForConnect) {
                   _context.next = 7;
                   break;
                 }
 
-                _logger.default.debug('Pending reconnection, skipping reconnect');
-
+                this.logger.debug('Pending reconnection, skipping reconnect');
                 if (immediate) this.retryManager.stopCurrentAttemptWaitingTime();
                 _context.next = 17;
                 break;
 
               case 7:
-                _logger.default.debug('No pending reconnection, will reconnect');
-
+                this.logger.debug('No pending reconnection, will reconnect');
                 _context.prev = 8;
                 this.waitingForConnect = true;
 
@@ -87459,13 +87141,19 @@ var CozyRealtime = /*#__PURE__*/function () {
   }, {
     key: "revokeWebSocket",
     value: function revokeWebSocket() {
+      var _this2 = this;
+
       this.emit('disconnected');
 
       if (this.hasWebSocket()) {
-        _logger.default.info('trashing the previous websocket…');
-
+        this.logger.info('trashing the previous websocket…');
         this.websocket.onmessage = null;
-        this.websocket.onerror = null;
+
+        this.websocket.onerror = function (err) {
+          // XXX: discard errors
+          _this2.logger.error("Error while trying to close the websocket: ".concat(err.message));
+        };
+
         this.websocket.onopen = null;
         this.websocket.onclose = null;
 
@@ -87476,7 +87164,7 @@ var CozyRealtime = /*#__PURE__*/function () {
           this.websocket = null;
         }
       } else {
-        _logger.default.error('Trying to revoke a websocket that is not attached. This should not happen');
+        this.logger.error('Trying to revoke a websocket that is not attached. This should not happen');
       }
     }
     /**
@@ -87488,8 +87176,7 @@ var CozyRealtime = /*#__PURE__*/function () {
     key: "stop",
     value: function stop() {
       if (this.isStarted) {
-        _logger.default.info('stopped');
-
+        this.logger.info('stopped');
         this.unsubscribeCordovaEvents();
         this.unsubscribeGlobalEvents();
         this.unsubscribeClientEvents();
@@ -87517,13 +87204,11 @@ var CozyRealtime = /*#__PURE__*/function () {
     value: function authenticate() {
       if (this.isWebSocketOpen()) {
         var token = (0, _utils.getToken)(this.client);
-
-        _logger.default.debug('authenticate with', token);
-
+        this.logger.debug('authenticate with', token);
         this.sendWebSocketMessage('AUTH', token);
         this.websocket.authenticated = true;
       } else {
-        _logger.default.error('Trying to authenticate to a non-opened websocket. This should not happen.', this.websocket, this.websocket && this.websocket.readyState);
+        this.logger.error('Trying to authenticate to a non-opened websocket. This should not happen.', this.websocket, this.websocket && this.websocket.readyState);
       }
     }
     /**
@@ -87582,8 +87267,7 @@ var CozyRealtime = /*#__PURE__*/function () {
     value: function send() {
       if (!this.sendDeprecationNoticeSent) {
         this.sendDeprecationNoticeSent = true;
-
-        _logger.default.warn('Deprecation notice: CozyRealtime.send() is deprecated, please use CozyRealtime.sendNotification() from now on');
+        this.logger.warn('Deprecation notice: CozyRealtime.send() is deprecated, please use CozyRealtime.sendNotification() from now on');
       }
 
       return this.sendNotification.apply(this, arguments);
@@ -87596,7 +87280,7 @@ var CozyRealtime = /*#__PURE__*/function () {
     key: "waitForSocketReady",
     value: function () {
       var _waitForSocketReady = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee4() {
-        var _this2 = this;
+        var _this3 = this;
 
         return _regenerator.default.wrap(function _callee4$(_context4) {
           while (1) {
@@ -87611,9 +87295,9 @@ var CozyRealtime = /*#__PURE__*/function () {
 
               case 2:
                 return _context4.abrupt("return", new Promise(function (resolve, reject) {
-                  _this2.once('ready', resolve);
+                  _this3.once('ready', resolve);
 
-                  _this2.once('close', reject);
+                  _this3.once('close', reject);
                 }));
 
               case 3:
@@ -87794,12 +87478,10 @@ var CozyRealtime = /*#__PURE__*/function () {
         } : {
           type: type
         };
-
-        _logger.default.debug('send subscription to', payload.type, payload.id);
-
+        this.logger.debug('send subscription to', payload.type, payload.id);
         this.sendWebSocketMessage('SUBSCRIBE', payload);
       } else {
-        _logger.default.error('Trying to subscribe on a non-ready socket. This should not happen. Type and id:', type, id);
+        this.logger.error('Trying to subscribe on a non-ready socket. This should not happen. Type and id:', type, id);
       }
     }
     /**
@@ -87820,12 +87502,10 @@ var CozyRealtime = /*#__PURE__*/function () {
         } : {
           type: type
         };
-
-        _logger.default.debug('send unsubscription to', payload.type, payload.id);
-
+        this.logger.debug('send unsubscription to', payload.type, payload.id);
         this.sendWebSocketMessage('UNSUBSCRIBE', payload);
       } else {
-        _logger.default.error('Trying to subscribe on a non-ready socket. This should not happen. Type and id:', type, id);
+        this.logger.error('Trying to subscribe on a non-ready socket. This should not happen. Type and id:', type, id);
       }
     }
     /**
@@ -87927,11 +87607,10 @@ var CozyRealtime = /*#__PURE__*/function () {
           event = _JSON$parse.event,
           payload = _JSON$parse.payload;
 
-      _logger.default.info('receive message from server', {
+      this.logger.info('receive message from server', {
         event: event,
         payload: payload
       });
-
       var handlers = this.subscriptions.getAllHandlersForEvent(event, payload.type, payload.id);
 
       var _iterator3 = _createForOfIteratorHelper(handlers),
@@ -87942,9 +87621,11 @@ var CozyRealtime = /*#__PURE__*/function () {
           var handler = _step3.value;
 
           try {
-            handler(payload.doc);
+            handler(_objectSpread(_objectSpread({}, payload.doc), {}, {
+              _type: payload.type
+            }));
           } catch (e) {
-            _logger.default.error("handler did throw for ".concat(event, ", ").concat(payload.type, ", ").concat(payload.id));
+            this.logger.error("handler did throw for ".concat(event, ", ").concat(payload.type, ", ").concat(payload.id));
           }
         }
       } catch (err) {
@@ -87954,7 +87635,7 @@ var CozyRealtime = /*#__PURE__*/function () {
       }
 
       if (event === 'error') {
-        _logger.default.warn('Stack returned an error', payload);
+        this.logger.warn('Stack returned an error', payload);
       }
     }
     /**
@@ -87965,8 +87646,7 @@ var CozyRealtime = /*#__PURE__*/function () {
   }, {
     key: "onWebSocketError",
     value: function onWebSocketError(error) {
-      _logger.default.info('An error was raised on the websocket', error);
-
+      this.logger.info('An error was raised on the websocket', error);
       this.retryManager.onFailure(error);
       this.reconnect();
     }
@@ -87991,8 +87671,7 @@ var CozyRealtime = /*#__PURE__*/function () {
   }, {
     key: "onWebSocketClose",
     value: function onWebSocketClose(event) {
-      _logger.default.info('The current websocket was closed by a third party', event);
-
+      this.logger.info('The current websocket was closed by a third party', event);
       this.retryManager.onFailure(event);
       this.reconnect();
     }
@@ -88034,7 +87713,7 @@ var CozyRealtime = /*#__PURE__*/function () {
   }, {
     key: "onClientLogin",
     value: function onClientLogin() {
-      _logger.default.info('Received login from cozy-client');
+      this.logger.info('Received login from cozy-client');
 
       if (this.isWebSocketOpen()) {
         this.authenticate(); // send subscriptions again, permissions may have changed
@@ -88172,8 +87851,7 @@ var CozyRealtime = /*#__PURE__*/function () {
   }, {
     key: "onOnline",
     value: function onOnline() {
-      _logger.default.info('reconnect because receiving an online event');
-
+      this.logger.info('reconnect because receiving an online event');
       this.reconnect({
         immediate: true
       });
@@ -88955,60 +88633,6 @@ module.exports = _toConsumableArray, module.exports.__esModule = true, module.ex
 
 /***/ }),
 
-/***/ "QJIl":
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.hasSafariPlugin = exports.hasNetworkInformationPlugin = exports.hasInAppBrowserPlugin = exports.hasDevicePlugin = void 0;
-
-var _cordova = __webpack_require__("dlno");
-
-var hasDevicePlugin = function hasDevicePlugin() {
-  return (0, _cordova.isCordova)() && window.device !== undefined;
-};
-
-exports.hasDevicePlugin = hasDevicePlugin;
-
-var hasInAppBrowserPlugin = function hasInAppBrowserPlugin() {
-  return (0, _cordova.isCordova)() && window.cordova.InAppBrowser !== undefined;
-};
-
-exports.hasInAppBrowserPlugin = hasInAppBrowserPlugin;
-
-var hasSafariPlugin = function hasSafariPlugin() {
-  return new Promise(function (resolve) {
-    if (!(0, _cordova.isCordova)() || window.SafariViewController === undefined) {
-      resolve(false);
-      return;
-    }
-
-    window.SafariViewController.isAvailable(function (available) {
-      return resolve(available);
-    });
-  });
-};
-/**
- * Check if the Cordova's cordova-plugin-network-information plugin is installed
- * @see https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-network-information/
- * @returns {boolean}
- */
-
-
-exports.hasSafariPlugin = hasSafariPlugin;
-
-var hasNetworkInformationPlugin = function hasNetworkInformationPlugin() {
-  return (0, _cordova.isCordova)() && window.navigator.connection !== undefined;
-};
-
-exports.hasNetworkInformationPlugin = hasNetworkInformationPlugin;
-
-/***/ }),
-
 /***/ "QJRf":
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
@@ -120037,26 +119661,6 @@ $export($export.S, 'Reflect', {
 });
 
 
-/***/ }),
-
-/***/ "dlno":
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.isCordova = void 0;
-
-// cordova
-var isCordova = function isCordova() {
-  return typeof window !== 'undefined' && window.cordova !== undefined;
-};
-
-exports.isCordova = isCordova;
-
 /***/ }),
 
 /***/ "dodI":
@@ -140818,54 +140422,6 @@ module.exports = JSON.parse("{\"country\":{\"stranger\":\"Étranger\"},\"Scan\":
 
 /***/ }),
 
-/***/ "lNPj":
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-var _interopRequireDefault = __webpack_require__("jm00");
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.isFlagshipApp = exports.getFlagshipMetadata = exports.FlagshipRoutes = void 0;
-
-var _cozyLogger = _interopRequireDefault(__webpack_require__("rUPj"));
-
-var FlagshipRoutes;
-exports.FlagshipRoutes = FlagshipRoutes;
-
-(function (FlagshipRoutes) {
-  FlagshipRoutes["Home"] = "home";
-  FlagshipRoutes["Cozyapp"] = "cozyapp";
-  FlagshipRoutes["Authenticate"] = "authenticate";
-  FlagshipRoutes["Onboarding"] = "onboarding";
-  FlagshipRoutes["Stack"] = "stack";
-})(FlagshipRoutes || (exports.FlagshipRoutes = FlagshipRoutes = {}));
-
-var getGlobalWindow = function getGlobalWindow() {
-  return typeof window !== 'undefined' ? window : ((0, _cozyLogger.default)('error', "\"window\" is not defined. This means that getGlobalWindow() shouldn't have been called and investigation should be done to prevent this call"), undefined);
-};
-
-var getFlagshipMetadata = function getFlagshipMetadata() {
-  var _getGlobalWindow$cozy, _getGlobalWindow, _getGlobalWindow$cozy2;
-
-  return (_getGlobalWindow$cozy = (_getGlobalWindow = getGlobalWindow()) === null || _getGlobalWindow === void 0 ? void 0 : (_getGlobalWindow$cozy2 = _getGlobalWindow.cozy) === null || _getGlobalWindow$cozy2 === void 0 ? void 0 : _getGlobalWindow$cozy2.flagship) !== null && _getGlobalWindow$cozy !== void 0 ? _getGlobalWindow$cozy : {};
-};
-
-exports.getFlagshipMetadata = getFlagshipMetadata;
-
-var isFlagshipApp = function isFlagshipApp() {
-  var _getGlobalWindow2, _getGlobalWindow2$coz;
-
-  return ((_getGlobalWindow2 = getGlobalWindow()) === null || _getGlobalWindow2 === void 0 ? void 0 : (_getGlobalWindow2$coz = _getGlobalWindow2.cozy) === null || _getGlobalWindow2$coz === void 0 ? void 0 : _getGlobalWindow2$coz.flagship) !== undefined;
-};
-
-exports.isFlagshipApp = isFlagshipApp;
-
-/***/ }),
-
 /***/ "lPmf":
 /***/ (function(module, exports, __webpack_require__) {
 
@@ -144610,64 +144166,6 @@ if(false) {}
 
 /***/ }),
 
-/***/ "m6Tf":
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-var _interopRequireDefault = __webpack_require__("jm00");
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.getDeviceName = void 0;
-
-var _capitalize = _interopRequireDefault(__webpack_require__("13ub"));
-
-var _cordova = __webpack_require__("dlno");
-
-var _plugins = __webpack_require__("QJIl");
-
-var _platform = __webpack_require__("AzAX");
-
-var DEFAULT_DEVICE = 'Device';
-
-// device
-var getAppleModel = function getAppleModel(identifier) {
-  var devices = ['iPhone', 'iPad', 'Watch', 'AppleTV'];
-
-  for (var _i = 0, _devices = devices; _i < _devices.length; _i++) {
-    var _device = _devices[_i];
-
-    if (identifier.match(new RegExp(_device))) {
-      return _device;
-    }
-  }
-
-  return DEFAULT_DEVICE;
-};
-
-var getDeviceName = function getDeviceName() {
-  if (!(0, _plugins.hasDevicePlugin)()) {
-    if ((0, _cordova.isCordova)()) {
-      console.warn('You should install `cordova-plugin-device`.'); // eslint-disable-line no-console
-    }
-
-    return DEFAULT_DEVICE;
-  }
-
-  var _window$device = window.device,
-      manufacturer = _window$device.manufacturer,
-      originalModel = _window$device.model;
-  var model = (0, _platform.isIOSApp)() ? getAppleModel(originalModel) : originalModel;
-  return "".concat((0, _capitalize.default)(manufacturer), " ").concat(model);
-};
-
-exports.getDeviceName = getDeviceName;
-
-/***/ }),
-
 /***/ "m7t9":
 /***/ (function(module, exports, __webpack_require__) {
 
@@ -170210,10 +169708,10 @@ var _createClass2 = _interopRequireDefault(__webpack_require__("Zvb3"));
 
 var _microee = _interopRequireDefault(__webpack_require__("GIvT"));
 
-var _logger = _interopRequireDefault(__webpack_require__("o2qs"));
-
 var _config = __webpack_require__("+RmU");
 
+var _logger = _interopRequireDefault(__webpack_require__("o2qs"));
+
 /**
  * This class creates an helper for processes that need
  * a retry mechanism with some wait between failed attempts.
@@ -170226,6 +169724,7 @@ var RetryManager = /*#__PURE__*/function () {
    * @param {number} baseWaitAfterFirstFailure - how much time in ms should we wait after the first failure?
    * @param {number} timeBeforeSuccessful - how much time should  we wait without error to acknowledge a success?
    * @param {number} raiseErrorAfterAttempts - after how many failed attempts should we raise an error?
+   * @param {object} logger A custom logger
    */
   function RetryManager() {
     var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
@@ -170236,9 +169735,12 @@ var RetryManager = /*#__PURE__*/function () {
         _ref$timeBeforeSucces = _ref.timeBeforeSuccessful,
         timeBeforeSuccessful = _ref$timeBeforeSucces === void 0 ? _config.timeBeforeSuccessful : _ref$timeBeforeSucces,
         _ref$raiseErrorAfterA = _ref.raiseErrorAfterAttempts,
-        raiseErrorAfterAttempts = _ref$raiseErrorAfterA === void 0 ? _config.raiseErrorAfterAttempts : _ref$raiseErrorAfterA;
+        raiseErrorAfterAttempts = _ref$raiseErrorAfterA === void 0 ? _config.raiseErrorAfterAttempts : _ref$raiseErrorAfterA,
+        _ref$logger = _ref.logger,
+        logger = _ref$logger === void 0 ? _logger.default : _ref$logger;
 
     (0, _classCallCheck2.default)(this, RetryManager);
+    this.logger = logger;
     this.reset();
     this.onSuccess = this.onSuccess.bind(this);
     this.onFailure = this.onFailure.bind(this);
@@ -170333,8 +169835,7 @@ var RetryManager = /*#__PURE__*/function () {
   }, {
     key: "onFailure",
     value: function onFailure(error) {
-      _logger.default.debug('failure, increase the failure counter of the retry manager');
-
+      this.logger.debug('failure, increase the failure counter of the retry manager');
       this.clearSuccessTimer();
       this.increaseFailureCounter();
       this.emit('failure', error);
@@ -170358,8 +169859,7 @@ var RetryManager = /*#__PURE__*/function () {
   }, {
     key: "reset",
     value: function reset() {
-      _logger.default.debug('reset the retry manager');
-
+      this.logger.debug('reset the retry manager');
       this.clearSuccessTimer();
       this.retries = 0;
       this.wait = 0;
@@ -170400,7 +169900,7 @@ var RetryManager = /*#__PURE__*/function () {
           while (1) {
             switch (_context.prev = _context.next) {
               case 0:
-                _logger.default.debug('waitBeforeNextAttempt', this.wait);
+                this.logger.debug('waitBeforeNextAttempt', this.wait);
 
                 if (!this.wait) {
                   _context.next = 5;
@@ -173519,13 +173019,13 @@ var _createClass2 = _interopRequireDefault(__webpack_require__("Zvb3"));
 
 var _isEqual = _interopRequireDefault(__webpack_require__("+KM7"));
 
-var _uniqWith = _interopRequireDefault(__webpack_require__("c20g"));
-
 var _pick = _interopRequireDefault(__webpack_require__("LF8A"));
 
+var _pullAt = _interopRequireDefault(__webpack_require__("tW+3"));
+
 var _remove2 = _interopRequireDefault(__webpack_require__("Jb16"));
 
-var _pullAt = _interopRequireDefault(__webpack_require__("tW+3"));
+var _uniqWith = _interopRequireDefault(__webpack_require__("c20g"));
 
 var _config = __webpack_require__("+RmU");
 
@@ -173549,8 +173049,10 @@ var _logger = _interopRequireDefault(__webpack_require__("o2qs"));
  */
 var SubscriptionList = /*#__PURE__*/function () {
   function SubscriptionList() {
+    var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
     (0, _classCallCheck2.default)(this, SubscriptionList);
     this.subscriptions = [];
+    this.logger = options.logger || _logger.default;
   }
   /**
    * Adds a subscription to the list
@@ -173569,7 +173071,9 @@ var SubscriptionList = /*#__PURE__*/function () {
       });
 
       if (found) {
-        _config.onDoubleSubscriptions && (0, _config.onDoubleSubscriptions)(subscription);
+        _config.onDoubleSubscriptions && (0, _config.onDoubleSubscriptions)(subscription, {
+          logger: this.logger
+        });
         if (!_config.allowDoubleSubscriptions) return;
       }
 
@@ -173604,7 +173108,7 @@ var SubscriptionList = /*#__PURE__*/function () {
         if (removed && removed.length > 0) return;
       }
 
-      _logger.default.warn('Trying to unsubscribe to an unknown subscription', subscription);
+      this.logger.warn('Trying to unsubscribe to an unknown subscription', subscription);
     }
     /**
      * Get all subscriptions
@@ -173716,8 +173220,7 @@ var SubscriptionList = /*#__PURE__*/function () {
     key: "normalize",
     value: function normalize(sub) {
       function error(msg) {
-        _logger.default.error(msg);
-
+        this.logger.error(msg);
         throw new Error(msg);
       }
 
@@ -177411,12 +176914,13 @@ var _interopRequireDefault = __webpack_require__("jm00");
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.doctype = void 0;
+exports.createWebSocket = createWebSocket;
 exports.getCozyClientFromOptions = getCozyClientFromOptions;
 exports.getToken = getToken;
 exports.getUrl = getUrl;
 exports.hasBrowserContext = void 0;
 exports.isOnline = isOnline;
+exports.protocol = void 0;
 
 var _has = _interopRequireDefault(__webpack_require__("sFVN"));
 
@@ -177428,19 +176932,20 @@ var _logger = _interopRequireDefault(__webpack_require__("o2qs"));
  */
 var hasBrowserContext = typeof window !== 'undefined';
 /**
- * The cozy Realtime doctype
+ * The cozy Realtime protocol name
+ * See https://github.com/cozy/cozy-stack/blob/master/docs/realtime.md
  * @type {string}
  */
 
 exports.hasBrowserContext = hasBrowserContext;
-var doctype = 'io.cozy.websocket';
+var protocol = 'io.cozy.websocket';
 /**
  * Returns if the navigator is online
  *
  * @returns {boolean} true if online or unknown
  */
 
-exports.doctype = doctype;
+exports.protocol = protocol;
 
 function isOnline() {
   var hasOnline = (0, _has.default)(global, 'navigator.onLine');
@@ -177510,16 +177015,22 @@ function getToken(client) {
 
 function getCozyClientFromOptions(_ref) {
   var cozyClient = _ref.cozyClient,
-      client = _ref.client;
+      client = _ref.client,
+      _ref$logger = _ref.logger,
+      logger = _ref$logger === void 0 ? _logger.default : _ref$logger;
 
   if (cozyClient) {
-    _logger.default.warn('Passing a `cozyClient` parameter is deprecated, please use `client` instead');
+    logger.warn('Passing a `cozyClient` parameter is deprecated, please use `client` instead');
   } else if (!client) {
-    _logger.default.warn('Realtime must be initialized with a client. Ex: `new Realtime({ client })`');
+    logger.warn('Realtime must be initialized with a client. Ex: `new Realtime({ client })`');
   }
 
   return client || cozyClient;
 }
+
+function createWebSocket(url, protocol) {
+  return new WebSocket(url, protocol);
+}
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__("aY11")))
 
 /***/ }),
@@ -182162,91 +181673,6 @@ var groupBy = createAggregator(function(result, value, key) {
 module.exports = groupBy;
 
 
-/***/ }),
-
-/***/ "tDKi":
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-var _interopRequireDefault = __webpack_require__("jm00");
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.nativeLinkOpen = void 0;
-
-var _regenerator = _interopRequireDefault(__webpack_require__("hJxD"));
-
-var _asyncToGenerator2 = _interopRequireDefault(__webpack_require__("HZZ/"));
-
-var _plugins = __webpack_require__("QJIl");
-
-var nativeLinkOpen = /*#__PURE__*/function () {
-  var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(_ref) {
-    var url, target, options;
-    return _regenerator.default.wrap(function _callee$(_context) {
-      while (1) {
-        switch (_context.prev = _context.next) {
-          case 0:
-            url = _ref.url;
-            _context.next = 3;
-            return (0, _plugins.hasSafariPlugin)();
-
-          case 3:
-            _context.t0 = _context.sent;
-
-            if (!_context.t0) {
-              _context.next = 6;
-              break;
-            }
-
-            _context.t0 = window.SafariViewController;
-
-          case 6:
-            if (!_context.t0) {
-              _context.next = 10;
-              break;
-            }
-
-            window.SafariViewController.show({
-              url: url,
-              transition: 'curl'
-            }, function (result) {
-              if (result.event === 'closed') {
-                window.SafariViewController.hide();
-              }
-            }, function () {
-              window.SafariViewController.hide();
-            });
-            _context.next = 11;
-            break;
-
-          case 10:
-            if ((0, _plugins.hasInAppBrowserPlugin)()) {
-              target = '_blank';
-              options = 'clearcache=yes,zoom=no';
-              window.cordova.InAppBrowser.open(url, target, options);
-            } else {
-              window.location = url;
-            }
-
-          case 11:
-          case "end":
-            return _context.stop();
-        }
-      }
-    }, _callee);
-  }));
-
-  return function nativeLinkOpen(_x) {
-    return _ref2.apply(this, arguments);
-  };
-}();
-
-exports.nativeLinkOpen = nativeLinkOpen;
-
 /***/ }),
 
 /***/ "tEOf":
@@ -196548,160 +195974,6 @@ __webpack_require__("AvzS")('Int16', 2, function (init) {
 });
 
 
-/***/ }),
-
-/***/ "yL+W":
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-var _interopRequireDefault = __webpack_require__("jm00");
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.startApp = exports.default = exports.checkApp = void 0;
-
-var _regenerator = _interopRequireDefault(__webpack_require__("hJxD"));
-
-var _asyncToGenerator2 = _interopRequireDefault(__webpack_require__("HZZ/"));
-
-var _platform = __webpack_require__("AzAX");
-
-var cordovaPluginIsInstalled = function cordovaPluginIsInstalled() {
-  return window.startApp;
-};
-
-/**
- * Normalize startApp params for Android and iOS
- */
-var getParams = function getParams(_ref) {
-  var appId = _ref.appId,
-      uri = _ref.uri;
-
-  if ((0, _platform.isAndroidApp)()) {
-    return {
-      package: appId
-    };
-  } else {
-    return uri;
-  }
-};
-
-var exported = {};
-/**
- * Start an application if it is installed on the phone
- * @returns Promise - False if the application was not able to be started
- */
-
-var startApp = exported.startApp = /*#__PURE__*/function () {
-  var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(appInfo) {
-    var startAppPlugin, isAppInstalled, params;
-    return _regenerator.default.wrap(function _callee$(_context) {
-      while (1) {
-        switch (_context.prev = _context.next) {
-          case 0:
-            startAppPlugin = window.startApp;
-            _context.next = 3;
-            return exported.checkApp(appInfo);
-
-          case 3:
-            isAppInstalled = _context.sent;
-
-            if (!isAppInstalled) {
-              _context.next = 9;
-              break;
-            }
-
-            params = getParams(appInfo);
-            return _context.abrupt("return", new Promise(function (resolve, reject) {
-              if (!cordovaPluginIsInstalled()) {
-                reject(new Error("Cordova plugin 'com.lampa.startapp' is not installed. This plugin is needed to start a native app. Required by cozy-bar"));
-                return;
-              }
-
-              startAppPlugin.set(params).start(resolve, reject);
-            }));
-
-          case 9:
-            return _context.abrupt("return", false);
-
-          case 10:
-          case "end":
-            return _context.stop();
-        }
-      }
-    }, _callee);
-  }));
-
-  return function (_x) {
-    return _ref2.apply(this, arguments);
-  };
-}();
-/**
- * Check that an application is installed on the phone
- * @returns Promise - Promise containing information on the application
- *
- * @example
- * > checkApp({ appId: 'io.cozy.drive.mobile', uri: 'cozydrive://' })
- * Promise.resolve({
- *  versionName: "0.9.2",
- *  packageName: "io.cozy.drive.mobile",
- *  versionCode: 902,
- *  applicationInfo: "ApplicationInfo{70aa0ef io.cozy.drive.mobile}"
- * })
- */
-
-
-exports.startApp = startApp;
-
-var checkApp = exported.checkApp = /*#__PURE__*/function () {
-  var _ref3 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(appInfo) {
-    var startAppPlugin, params;
-    return _regenerator.default.wrap(function _callee2$(_context2) {
-      while (1) {
-        switch (_context2.prev = _context2.next) {
-          case 0:
-            startAppPlugin = window.startApp;
-            params = getParams(appInfo);
-            return _context2.abrupt("return", new Promise(function (resolve, reject) {
-              if (!cordovaPluginIsInstalled()) {
-                reject(new Error("Cordova plugin 'com.lampa.startapp' is not installed."));
-                return;
-              }
-
-              startAppPlugin.set(params).check(function (infos) {
-                return resolve(infos === 'OK' ? true : infos);
-              }, function (error) {
-                if (error === false || error.indexOf('NameNotFoundException') === 0) {
-                  // Plugin returns an error 'NameNotFoundException' on Android and
-                  // false on iOS when an application is not found.
-                  // We prefer to always return false
-                  resolve(false);
-                } else {
-                  reject(error);
-                }
-              });
-            }));
-
-          case 3:
-          case "end":
-            return _context2.stop();
-        }
-      }
-    }, _callee2);
-  }));
-
-  return function (_x2) {
-    return _ref3.apply(this, arguments);
-  };
-}();
-
-exports.checkApp = checkApp;
-var _default = exported;
-exports.default = _default;
-
 /***/ }),
 
 /***/ "yP9i":
diff --git a/public/index.html b/public/index.html
index 2b3debb1d7d5bee9895eebc404cb3733dcd9117b..e29a24a469ac79c474d888f475254cd9cf574c2f 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1 +1 @@
-<!DOCTYPE html><html lang="{{.Locale}}"><head><meta charset="utf-8"><title>Ecolyo | Désabonnement</title><link rel="icon" type="image/png" href="public/favicon-32x32.png" sizes="32x32"><link rel="icon" type="image/png" href="public/favicon-16x16.png" sizes="16x16"><!-- PWA Manifest --><link rel="mask-icon" href="public/safari-pinned-tab.svg" color="#297EF2"><meta name="viewport" content="width=device-width,height=device-height,initial-scale=1,viewport-fit=cover"><!-- PWA iOS --><link rel="apple-touch-icon" sizes="180x180" href="public/apple-touch-icon.png"><link rel="apple-touch-startup-image" href="public/apple-touch-icon.png"><meta name="apple-mobile-web-app-title" content="Ecolyo"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><!-- PWA Colors --><meta name="theme-color" content="#343641"><meta name="background-color" content="#121212">{{.ThemeCSS}} {{.CozyBar}}<script src="//{{.Domain}}/assets/js/piwik.js"></script></head><body><div role="application" class="application" data-cozy="{{.CozyData}}"><script src="../public/ecolyo.297fa83f17e3216f8d1a.js"></script></div></body></html>
\ No newline at end of file
+<!DOCTYPE html><html lang="{{.Locale}}"><head><meta charset="utf-8"><title>Ecolyo | Désabonnement</title><link rel="icon" type="image/png" href="public/favicon-32x32.png" sizes="32x32"><link rel="icon" type="image/png" href="public/favicon-16x16.png" sizes="16x16"><!-- PWA Manifest --><link rel="mask-icon" href="public/safari-pinned-tab.svg" color="#297EF2"><meta name="viewport" content="width=device-width,height=device-height,initial-scale=1,viewport-fit=cover"><!-- PWA iOS --><link rel="apple-touch-icon" sizes="180x180" href="public/apple-touch-icon.png"><link rel="apple-touch-startup-image" href="public/apple-touch-icon.png"><meta name="apple-mobile-web-app-title" content="Ecolyo"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><!-- PWA Colors --><meta name="theme-color" content="#343641"><meta name="background-color" content="#121212">{{.ThemeCSS}} {{.CozyBar}}<script src="//{{.Domain}}/assets/js/piwik.js"></script></head><body><div role="application" class="application" data-cozy="{{.CozyData}}"><script src="../public/ecolyo.4555ae0963be5bd89ab7.js"></script></div></body></html>
\ No newline at end of file
diff --git a/vendors/ecolyo.0f66f88c2aa7f446186b.js b/vendors/ecolyo.84c5768e0b0aa3838025.js
similarity index 99%
rename from vendors/ecolyo.0f66f88c2aa7f446186b.js
rename to vendors/ecolyo.84c5768e0b0aa3838025.js
index 571458bed006763b5e1f831f3979113689cb0a2e..7e334f7d83a94e771cafff5dd6eaeb1465460b4a 100644
--- a/vendors/ecolyo.0f66f88c2aa7f446186b.js
+++ b/vendors/ecolyo.84c5768e0b0aa3838025.js
@@ -2953,19 +2953,22 @@ var requireDoubleUnsubscriptions = true;
 exports.requireDoubleUnsubscriptions = requireDoubleUnsubscriptions;
 
 var onDoubleSubscriptions = function onDoubleSubscriptions(subscription) {
-  _logger.default.warn('Double subscription for ', subscription);
+  var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
+      _ref$logger = _ref.logger,
+      logger = _ref$logger === void 0 ? _logger.default : _ref$logger;
 
-  if (allowDoubleSubscriptions) {
-    _logger.default.info('The handler may be called twice for the same event!');
+  logger.warn('Double subscription for ', subscription);
 
-    _logger.default.info('Remember to call one `unsubscribe` for each `subscribe`');
+  if (allowDoubleSubscriptions) {
+    logger.info('The handler may be called twice for the same event!');
+    logger.info('Remember to call one `unsubscribe` for each `subscribe`');
   } else {
-    _logger.default.info('The handler will only be called once');
+    logger.info('The handler will only be called once');
 
     if (requireDoubleUnsubscriptions) {
-      _logger.default.info('Remember to call one `unsubscribe` for each `subscribe`');
+      logger.info('Remember to call one `unsubscribe` for each `subscribe`');
     } else {
-      _logger.default.info('`unsubscribe` will remove all similar subscriptions');
+      logger.info('`unsubscribe` will remove all similar subscriptions');
     }
   }
 };
@@ -8942,154 +8945,6 @@ function startOfWeek (dirtyDate, dirtyOptions) {
 module.exports = startOfWeek
 
 
-/***/ }),
-
-/***/ "/K1E":
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-Object.defineProperty(exports, "checkApp", {
-  enumerable: true,
-  get: function get() {
-    return _apps.checkApp;
-  }
-});
-Object.defineProperty(exports, "getDeviceName", {
-  enumerable: true,
-  get: function get() {
-    return _device.getDeviceName;
-  }
-});
-Object.defineProperty(exports, "getFlagshipMetadata", {
-  enumerable: true,
-  get: function get() {
-    return _flagship.getFlagshipMetadata;
-  }
-});
-Object.defineProperty(exports, "getPlatform", {
-  enumerable: true,
-  get: function get() {
-    return _platform.getPlatform;
-  }
-});
-Object.defineProperty(exports, "hasDevicePlugin", {
-  enumerable: true,
-  get: function get() {
-    return _plugins.hasDevicePlugin;
-  }
-});
-Object.defineProperty(exports, "hasInAppBrowserPlugin", {
-  enumerable: true,
-  get: function get() {
-    return _plugins.hasInAppBrowserPlugin;
-  }
-});
-Object.defineProperty(exports, "hasNetworkInformationPlugin", {
-  enumerable: true,
-  get: function get() {
-    return _plugins.hasNetworkInformationPlugin;
-  }
-});
-Object.defineProperty(exports, "hasSafariPlugin", {
-  enumerable: true,
-  get: function get() {
-    return _plugins.hasSafariPlugin;
-  }
-});
-Object.defineProperty(exports, "isAndroid", {
-  enumerable: true,
-  get: function get() {
-    return _platform.isAndroid;
-  }
-});
-Object.defineProperty(exports, "isAndroidApp", {
-  enumerable: true,
-  get: function get() {
-    return _platform.isAndroidApp;
-  }
-});
-Object.defineProperty(exports, "isCordova", {
-  enumerable: true,
-  get: function get() {
-    return _cordova.isCordova;
-  }
-});
-Object.defineProperty(exports, "isFlagshipApp", {
-  enumerable: true,
-  get: function get() {
-    return _flagship.isFlagshipApp;
-  }
-});
-Object.defineProperty(exports, "isIOS", {
-  enumerable: true,
-  get: function get() {
-    return _platform.isIOS;
-  }
-});
-Object.defineProperty(exports, "isIOSApp", {
-  enumerable: true,
-  get: function get() {
-    return _platform.isIOSApp;
-  }
-});
-Object.defineProperty(exports, "isMobile", {
-  enumerable: true,
-  get: function get() {
-    return _platform.isMobile;
-  }
-});
-Object.defineProperty(exports, "isMobileApp", {
-  enumerable: true,
-  get: function get() {
-    return _platform.isMobileApp;
-  }
-});
-Object.defineProperty(exports, "isWebApp", {
-  enumerable: true,
-  get: function get() {
-    return _platform.isWebApp;
-  }
-});
-Object.defineProperty(exports, "nativeLinkOpen", {
-  enumerable: true,
-  get: function get() {
-    return _link.nativeLinkOpen;
-  }
-});
-Object.defineProperty(exports, "openDeeplinkOrRedirect", {
-  enumerable: true,
-  get: function get() {
-    return _deeplink.openDeeplinkOrRedirect;
-  }
-});
-Object.defineProperty(exports, "startApp", {
-  enumerable: true,
-  get: function get() {
-    return _apps.startApp;
-  }
-});
-
-var _platform = __webpack_require__("AzAX");
-
-var _device = __webpack_require__("m6Tf");
-
-var _apps = __webpack_require__("yL+W");
-
-var _plugins = __webpack_require__("QJIl");
-
-var _cordova = __webpack_require__("dlno");
-
-var _link = __webpack_require__("tDKi");
-
-var _deeplink = __webpack_require__("AIES");
-
-var _flagship = __webpack_require__("lNPj");
-
 /***/ }),
 
 /***/ "/Kh4":
@@ -36346,6 +36201,11 @@ var _createClass2 = _interopRequireDefault(__webpack_require__("Zvb3"));
 
 var _CozyRealtime = _interopRequireDefault(__webpack_require__("PdKc"));
 
+/**
+ * A cozy-client instance.
+ * @typedef {import("cozy-client/dist/index").CozyClient} CozyClient
+ */
+
 /**
  * Realtime plugin for cozy-client
  *
@@ -36360,10 +36220,16 @@ var RealtimePlugin = /*#__PURE__*/function () {
    *
    * @constructor
    * @param {CozyClient} client A cozy-client instance
+   * @param {object} options
+   * @param {Function} [options.createWebSocket] The function used to create WebSocket instances
+   * @param {object} [options.logger] A custom logger for CozyRealtime
    */
   function RealtimePlugin(client) {
+    var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
     (0, _classCallCheck2.default)(this, RealtimePlugin);
     this.client = client;
+    this.createWebSocket = options.createWebSocket;
+    this.logger = options.logger;
     this.realtime = null;
     this.handleLogin = this.handleLogin.bind(this);
     this.handleLogout = this.handleLogout.bind(this);
@@ -36376,7 +36242,9 @@ var RealtimePlugin = /*#__PURE__*/function () {
     key: "handleLogin",
     value: function handleLogin() {
       this.realtime = new _CozyRealtime.default({
-        client: this.client
+        client: this.client,
+        createWebSocket: this.createWebSocket,
+        logger: this.logger
       });
       this.client.emit('plugin:realtime:login');
     }
@@ -84749,129 +84617,6 @@ exports.default = _default;
 
 /***/ }),
 
-/***/ "AIES":
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.openDeeplinkOrRedirect = void 0;
-
-/**
- * This file is used to open the native app from a webapp
- * if this native app is installed
- *
- * From a webapp, we don't have any clue if a native app is installed.
- * The only way to know that, is to try to open the custom link
- * (aka cozydrive://) and if nothing happens (no blur) we redirect to
- * the callback
- *
- * Firefox tries to open custom link, so we need to create an iframe
- * to detect if this is supported or not
- */
-var _createHiddenIframe = function _createHiddenIframe(target, uri, randomId) {
-  var iframe = document.createElement('iframe');
-  iframe.src = uri;
-  iframe.id = "hiddenIframe_".concat(randomId);
-  iframe.style.display = 'none';
-  target.appendChild(iframe);
-  return iframe;
-};
-
-var openUriWithHiddenFrame = function openUriWithHiddenFrame(uri, failCb) {
-  var randomId = Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);
-  window.addEventListener('blur', onBlur);
-
-  var iframe = _createHiddenIframe(document.body, 'about:blank', randomId);
-
-  var timeout = setTimeout(function () {
-    failCb();
-    window.removeEventListener('blur', onBlur);
-    iframe.parentElement.removeChild(iframe);
-  }, 500);
-
-  function onBlur() {
-    clearTimeout(timeout);
-    window.removeEventListener('blur', onBlur);
-    iframe.parentElement.removeChild(iframe);
-  }
-
-  iframe.contentWindow.location.href = uri;
-};
-
-var openUriWithTimeoutHack = function openUriWithTimeoutHack(uri, failCb) {
-  var timeout = setTimeout(function () {
-    failCb();
-    target.removeEventListener('blur', onBlur);
-  }, 500); // handle page running in an iframe (blur must be registered with top level window)
-
-  var target = window;
-
-  while (target != target.parent) {
-    target = target.parent;
-  }
-
-  target.addEventListener('blur', onBlur);
-
-  function onBlur() {
-    clearTimeout(timeout);
-    target.removeEventListener('blur', onBlur);
-  } // Why is an uri assigned to location object?
-
-
-  window.location = uri;
-};
-
-var openUriWithMsLaunchUri = function openUriWithMsLaunchUri(uri, failCb) {
-  navigator.msLaunchUri(uri, undefined, failCb);
-};
-
-var checkBrowser = function checkBrowser() {
-  var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
-  var ua = navigator.userAgent.toLowerCase();
-  var isSafari = ua.includes('safari') && !ua.includes('chrome') || Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0;
-  var isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
-  var isIOS122 = isIOS && (ua.includes('os 12_2') || ua.includes('os 12_3'));
-  return {
-    isOpera: isOpera,
-    isFirefox: typeof window.InstallTrigger !== 'undefined',
-    isSafari: isSafari,
-    isChrome: !!window.chrome && !isOpera,
-    isIOS122: isIOS122,
-    isIOS: isIOS
-  };
-};
-/**
- *
- * @param {String} deeplink (cozydrive://)
- * @param {String} failCb (http://drive.cozy.ios)
- */
-
-
-var openDeeplinkOrRedirect = function openDeeplinkOrRedirect(deeplink, failCb) {
-  if (navigator.msLaunchUri) {
-    // for IE and Edge in Win 8 and Win 10
-    openUriWithMsLaunchUri(deeplink, failCb);
-  } else {
-    var browser = checkBrowser();
-
-    if (browser.isChrome || browser.isIOS && !browser.isIOS122) {
-      openUriWithTimeoutHack(deeplink, failCb);
-    } else if (browser.isSafari && !browser.isIOS122 || browser.isFirefox) {
-      openUriWithHiddenFrame(deeplink, failCb);
-    } else {
-      failCb();
-    }
-  }
-};
-
-exports.openDeeplinkOrRedirect = openDeeplinkOrRedirect;
-
-/***/ }),
-
 /***/ "AJH6":
 /***/ (function(module, exports) {
 
@@ -88868,80 +88613,6 @@ module.exports = Permission;
 
 /***/ }),
 
-/***/ "AzAX":
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.isWebApp = exports.isMobileApp = exports.isMobile = exports.isIOSApp = exports.isIOS = exports.isAndroidApp = exports.isAndroid = exports.getPlatform = void 0;
-
-var _cordova = __webpack_require__("dlno");
-
-var ANDROID_PLATFORM = 'android';
-var IOS_PLATFORM = 'ios';
-var WEB_PLATFORM = 'web';
-
-var getPlatform = function getPlatform() {
-  return (0, _cordova.isCordova)() ? window.cordova.platformId : WEB_PLATFORM;
-};
-
-exports.getPlatform = getPlatform;
-
-var isPlatform = function isPlatform(platform) {
-  return getPlatform() === platform;
-};
-
-var isIOSApp = function isIOSApp() {
-  return isPlatform(IOS_PLATFORM);
-};
-
-exports.isIOSApp = isIOSApp;
-
-var isAndroidApp = function isAndroidApp() {
-  return isPlatform(ANDROID_PLATFORM);
-};
-
-exports.isAndroidApp = isAndroidApp;
-
-var isWebApp = function isWebApp() {
-  return isPlatform(WEB_PLATFORM);
-};
-
-exports.isWebApp = isWebApp;
-
-var isMobileApp = function isMobileApp() {
-  return (0, _cordova.isCordova)();
-}; // return if is on an Android Device (native or browser)
-
-
-exports.isMobileApp = isMobileApp;
-
-var isAndroid = function isAndroid() {
-  return window.navigator.userAgent && window.navigator.userAgent.indexOf('Android') >= 0;
-}; // return if is on an iOS Device (native or browser)
-
-
-exports.isAndroid = isAndroid;
-
-var isIOS = function isIOS() {
-  return window.navigator.userAgent && /iPad|iPhone|iPod/.test(window.navigator.userAgent);
-}; // isMobile checks if the user is on a smartphone : native app or browser
-
-
-exports.isIOS = isIOS;
-
-var isMobile = function isMobile() {
-  return isAndroid() || isIOS();
-};
-
-exports.isMobile = isMobile;
-
-/***/ }),
-
 /***/ "Aznx":
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
@@ -189100,6 +188771,8 @@ exports.default = void 0;
 
 var _regenerator = _interopRequireDefault(__webpack_require__("hJxD"));
 
+var _defineProperty2 = _interopRequireDefault(__webpack_require__("J58c"));
+
 var _asyncToGenerator2 = _interopRequireDefault(__webpack_require__("HZZ/"));
 
 var _classCallCheck2 = _interopRequireDefault(__webpack_require__("GeFe"));
@@ -189108,17 +188781,21 @@ var _createClass2 = _interopRequireDefault(__webpack_require__("Zvb3"));
 
 var _microee = _interopRequireDefault(__webpack_require__("GIvT"));
 
-var _cozyDeviceHelper = __webpack_require__("/K1E");
+var _cozyDeviceHelper = __webpack_require__("Kv7L");
 
-var _logger = _interopRequireDefault(__webpack_require__("o2qs"));
+var _RetryManager = _interopRequireDefault(__webpack_require__("oeo/"));
 
 var _SubscriptionList = _interopRequireDefault(__webpack_require__("q5BW"));
 
-var _RetryManager = _interopRequireDefault(__webpack_require__("oeo/"));
+var _config = __webpack_require__("+RmU");
+
+var _logger = _interopRequireDefault(__webpack_require__("o2qs"));
 
 var _utils = __webpack_require__("rO7t");
 
-var _config = __webpack_require__("+RmU");
+function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
+
+function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
 
 function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
 
@@ -189126,6 +188803,11 @@ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o =
 
 function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
 
+/**
+ * A cozy-client instance.
+ * @typedef {import("cozy-client/dist/index").CozyClient} CozyClient
+ */
+
 /**
  * Manage the realtime interactions with a cozy stack
  */
@@ -189133,19 +188815,26 @@ var CozyRealtime = /*#__PURE__*/function () {
   /**
    * @constructor
    * @param {object} options
-   * @param {CozyClient}  options.client
+   * @param {CozyClient}  options.client A cozy-client instance
+   * @param {Function} [options.createWebSocket] The function used to create WebSocket instances
+   * @param {object} [options.logger] A custom logger
    */
   function CozyRealtime(options) {
     var _this = this;
 
     (0, _classCallCheck2.default)(this, CozyRealtime);
     this.client = (0, _utils.getCozyClientFromOptions)(options);
-    this.subscriptions = new _SubscriptionList.default();
+    this.createWebSocket = options.createWebSocket || _utils.createWebSocket;
+    this.logger = options.logger || _logger.default;
+    this.subscriptions = new _SubscriptionList.default({
+      logger: options.logger
+    });
     this.retryManager = new _RetryManager.default({
       raiseErrorAfterAttempts: _config.raiseErrorAfterAttempts,
       timeBeforeSuccessful: _config.timeBeforeSuccessful,
       baseWaitAfterFirstFailure: _config.baseWaitAfterFirstFailure,
-      maxWaitBetweenRetries: _config.maxWaitBetweenRetries
+      maxWaitBetweenRetries: _config.maxWaitBetweenRetries,
+      logger: options.logger
     });
     this.retryManager.on('error', function (err) {
       return _this.emit('error', err);
@@ -189153,7 +188842,7 @@ var CozyRealtime = /*#__PURE__*/function () {
     this.bindEventHandlers();
 
     if ((0, _cozyDeviceHelper.isCordova)() && !(0, _cozyDeviceHelper.hasNetworkInformationPlugin)()) {
-      _logger.default.warn("This seems a Cordova app and cordova-plugin-network-information doesn't seem to be installed. Please install it from https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-network-information/ to support online and offline events.");
+      this.logger.warn("This seems a Cordova app and cordova-plugin-network-information doesn't seem to be installed. Please install it from https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-network-information/ to support online and offline events.");
     }
   }
   /**
@@ -189169,8 +188858,7 @@ var CozyRealtime = /*#__PURE__*/function () {
     key: "start",
     value: function start() {
       if (!this.isStarted) {
-        _logger.default.info('started');
-
+        this.logger.info('started');
         this.isStarted = true;
         this.retryManager.reset();
         this.subscribeGlobalEvents();
@@ -189191,17 +188879,15 @@ var CozyRealtime = /*#__PURE__*/function () {
     key: "createSocket",
     value: function createSocket() {
       if (this.websocket) {
-        _logger.default.error('Unexpected replacement of an existing socket, this should not happen. A `revokeWebSocket()` should have been called first inside CozyRealtime');
-
+        this.logger.error('Unexpected replacement of an existing socket, this should not happen. A `revokeWebSocket()` should have been called first inside CozyRealtime');
         this.revokeWebSocket();
       }
 
-      _logger.default.info('creating a new websocket…');
-
+      this.logger.info('creating a new websocket…');
       var url = (0, _utils.getUrl)(this.client);
 
       try {
-        this.websocket = new WebSocket(url, _utils.doctype);
+        this.websocket = this.createWebSocket(url, _utils.protocol);
         this.websocket.authenticated = false;
         this.websocket.onmessage = this.onWebSocketMessage;
         this.websocket.onerror = this.onWebSocketError;
@@ -189231,24 +188917,20 @@ var CozyRealtime = /*#__PURE__*/function () {
             switch (_context.prev = _context.next) {
               case 0:
                 _ref = _args.length > 0 && _args[0] !== undefined ? _args[0] : {}, _ref$immediate = _ref.immediate, immediate = _ref$immediate === void 0 ? false : _ref$immediate;
-
-                _logger.default.info('connecting…'); // avoid multiple concurrent calls to connect, keeps the first one
-
+                this.logger.info('connecting…'); // avoid multiple concurrent calls to connect, keeps the first one
 
                 if (!this.waitingForConnect) {
                   _context.next = 7;
                   break;
                 }
 
-                _logger.default.debug('Pending reconnection, skipping reconnect');
-
+                this.logger.debug('Pending reconnection, skipping reconnect');
                 if (immediate) this.retryManager.stopCurrentAttemptWaitingTime();
                 _context.next = 17;
                 break;
 
               case 7:
-                _logger.default.debug('No pending reconnection, will reconnect');
-
+                this.logger.debug('No pending reconnection, will reconnect');
                 _context.prev = 8;
                 this.waitingForConnect = true;
 
@@ -189328,13 +189010,19 @@ var CozyRealtime = /*#__PURE__*/function () {
   }, {
     key: "revokeWebSocket",
     value: function revokeWebSocket() {
+      var _this2 = this;
+
       this.emit('disconnected');
 
       if (this.hasWebSocket()) {
-        _logger.default.info('trashing the previous websocket…');
-
+        this.logger.info('trashing the previous websocket…');
         this.websocket.onmessage = null;
-        this.websocket.onerror = null;
+
+        this.websocket.onerror = function (err) {
+          // XXX: discard errors
+          _this2.logger.error("Error while trying to close the websocket: ".concat(err.message));
+        };
+
         this.websocket.onopen = null;
         this.websocket.onclose = null;
 
@@ -189345,7 +189033,7 @@ var CozyRealtime = /*#__PURE__*/function () {
           this.websocket = null;
         }
       } else {
-        _logger.default.error('Trying to revoke a websocket that is not attached. This should not happen');
+        this.logger.error('Trying to revoke a websocket that is not attached. This should not happen');
       }
     }
     /**
@@ -189357,8 +189045,7 @@ var CozyRealtime = /*#__PURE__*/function () {
     key: "stop",
     value: function stop() {
       if (this.isStarted) {
-        _logger.default.info('stopped');
-
+        this.logger.info('stopped');
         this.unsubscribeCordovaEvents();
         this.unsubscribeGlobalEvents();
         this.unsubscribeClientEvents();
@@ -189386,13 +189073,11 @@ var CozyRealtime = /*#__PURE__*/function () {
     value: function authenticate() {
       if (this.isWebSocketOpen()) {
         var token = (0, _utils.getToken)(this.client);
-
-        _logger.default.debug('authenticate with', token);
-
+        this.logger.debug('authenticate with', token);
         this.sendWebSocketMessage('AUTH', token);
         this.websocket.authenticated = true;
       } else {
-        _logger.default.error('Trying to authenticate to a non-opened websocket. This should not happen.', this.websocket, this.websocket && this.websocket.readyState);
+        this.logger.error('Trying to authenticate to a non-opened websocket. This should not happen.', this.websocket, this.websocket && this.websocket.readyState);
       }
     }
     /**
@@ -189451,8 +189136,7 @@ var CozyRealtime = /*#__PURE__*/function () {
     value: function send() {
       if (!this.sendDeprecationNoticeSent) {
         this.sendDeprecationNoticeSent = true;
-
-        _logger.default.warn('Deprecation notice: CozyRealtime.send() is deprecated, please use CozyRealtime.sendNotification() from now on');
+        this.logger.warn('Deprecation notice: CozyRealtime.send() is deprecated, please use CozyRealtime.sendNotification() from now on');
       }
 
       return this.sendNotification.apply(this, arguments);
@@ -189465,7 +189149,7 @@ var CozyRealtime = /*#__PURE__*/function () {
     key: "waitForSocketReady",
     value: function () {
       var _waitForSocketReady = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee4() {
-        var _this2 = this;
+        var _this3 = this;
 
         return _regenerator.default.wrap(function _callee4$(_context4) {
           while (1) {
@@ -189480,9 +189164,9 @@ var CozyRealtime = /*#__PURE__*/function () {
 
               case 2:
                 return _context4.abrupt("return", new Promise(function (resolve, reject) {
-                  _this2.once('ready', resolve);
+                  _this3.once('ready', resolve);
 
-                  _this2.once('close', reject);
+                  _this3.once('close', reject);
                 }));
 
               case 3:
@@ -189663,12 +189347,10 @@ var CozyRealtime = /*#__PURE__*/function () {
         } : {
           type: type
         };
-
-        _logger.default.debug('send subscription to', payload.type, payload.id);
-
+        this.logger.debug('send subscription to', payload.type, payload.id);
         this.sendWebSocketMessage('SUBSCRIBE', payload);
       } else {
-        _logger.default.error('Trying to subscribe on a non-ready socket. This should not happen. Type and id:', type, id);
+        this.logger.error('Trying to subscribe on a non-ready socket. This should not happen. Type and id:', type, id);
       }
     }
     /**
@@ -189689,12 +189371,10 @@ var CozyRealtime = /*#__PURE__*/function () {
         } : {
           type: type
         };
-
-        _logger.default.debug('send unsubscription to', payload.type, payload.id);
-
+        this.logger.debug('send unsubscription to', payload.type, payload.id);
         this.sendWebSocketMessage('UNSUBSCRIBE', payload);
       } else {
-        _logger.default.error('Trying to subscribe on a non-ready socket. This should not happen. Type and id:', type, id);
+        this.logger.error('Trying to subscribe on a non-ready socket. This should not happen. Type and id:', type, id);
       }
     }
     /**
@@ -189796,11 +189476,10 @@ var CozyRealtime = /*#__PURE__*/function () {
           event = _JSON$parse.event,
           payload = _JSON$parse.payload;
 
-      _logger.default.info('receive message from server', {
+      this.logger.info('receive message from server', {
         event: event,
         payload: payload
       });
-
       var handlers = this.subscriptions.getAllHandlersForEvent(event, payload.type, payload.id);
 
       var _iterator3 = _createForOfIteratorHelper(handlers),
@@ -189811,9 +189490,11 @@ var CozyRealtime = /*#__PURE__*/function () {
           var handler = _step3.value;
 
           try {
-            handler(payload.doc);
+            handler(_objectSpread(_objectSpread({}, payload.doc), {}, {
+              _type: payload.type
+            }));
           } catch (e) {
-            _logger.default.error("handler did throw for ".concat(event, ", ").concat(payload.type, ", ").concat(payload.id));
+            this.logger.error("handler did throw for ".concat(event, ", ").concat(payload.type, ", ").concat(payload.id));
           }
         }
       } catch (err) {
@@ -189823,7 +189504,7 @@ var CozyRealtime = /*#__PURE__*/function () {
       }
 
       if (event === 'error') {
-        _logger.default.warn('Stack returned an error', payload);
+        this.logger.warn('Stack returned an error', payload);
       }
     }
     /**
@@ -189834,8 +189515,7 @@ var CozyRealtime = /*#__PURE__*/function () {
   }, {
     key: "onWebSocketError",
     value: function onWebSocketError(error) {
-      _logger.default.info('An error was raised on the websocket', error);
-
+      this.logger.info('An error was raised on the websocket', error);
       this.retryManager.onFailure(error);
       this.reconnect();
     }
@@ -189860,8 +189540,7 @@ var CozyRealtime = /*#__PURE__*/function () {
   }, {
     key: "onWebSocketClose",
     value: function onWebSocketClose(event) {
-      _logger.default.info('The current websocket was closed by a third party', event);
-
+      this.logger.info('The current websocket was closed by a third party', event);
       this.retryManager.onFailure(event);
       this.reconnect();
     }
@@ -189903,7 +189582,7 @@ var CozyRealtime = /*#__PURE__*/function () {
   }, {
     key: "onClientLogin",
     value: function onClientLogin() {
-      _logger.default.info('Received login from cozy-client');
+      this.logger.info('Received login from cozy-client');
 
       if (this.isWebSocketOpen()) {
         this.authenticate(); // send subscriptions again, permissions may have changed
@@ -190041,8 +189720,7 @@ var CozyRealtime = /*#__PURE__*/function () {
   }, {
     key: "onOnline",
     value: function onOnline() {
-      _logger.default.info('reconnect because receiving an online event');
-
+      this.logger.info('reconnect because receiving an online event');
       this.reconnect({
         immediate: true
       });
@@ -193156,60 +192834,6 @@ exports.default = _default;
 
 /***/ }),
 
-/***/ "QJIl":
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.hasSafariPlugin = exports.hasNetworkInformationPlugin = exports.hasInAppBrowserPlugin = exports.hasDevicePlugin = void 0;
-
-var _cordova = __webpack_require__("dlno");
-
-var hasDevicePlugin = function hasDevicePlugin() {
-  return (0, _cordova.isCordova)() && window.device !== undefined;
-};
-
-exports.hasDevicePlugin = hasDevicePlugin;
-
-var hasInAppBrowserPlugin = function hasInAppBrowserPlugin() {
-  return (0, _cordova.isCordova)() && window.cordova.InAppBrowser !== undefined;
-};
-
-exports.hasInAppBrowserPlugin = hasInAppBrowserPlugin;
-
-var hasSafariPlugin = function hasSafariPlugin() {
-  return new Promise(function (resolve) {
-    if (!(0, _cordova.isCordova)() || window.SafariViewController === undefined) {
-      resolve(false);
-      return;
-    }
-
-    window.SafariViewController.isAvailable(function (available) {
-      return resolve(available);
-    });
-  });
-};
-/**
- * Check if the Cordova's cordova-plugin-network-information plugin is installed
- * @see https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-network-information/
- * @returns {boolean}
- */
-
-
-exports.hasSafariPlugin = hasSafariPlugin;
-
-var hasNetworkInformationPlugin = function hasNetworkInformationPlugin() {
-  return (0, _cordova.isCordova)() && window.navigator.connection !== undefined;
-};
-
-exports.hasNetworkInformationPlugin = hasNetworkInformationPlugin;
-
-/***/ }),
-
 /***/ "QJRf":
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
@@ -271507,26 +271131,6 @@ parse.json = json;
 
 /***/ }),
 
-/***/ "dlno":
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.isCordova = void 0;
-
-// cordova
-var isCordova = function isCordova() {
-  return typeof window !== 'undefined' && window.cordova !== undefined;
-};
-
-exports.isCordova = isCordova;
-
-/***/ }),
-
 /***/ "dmTB":
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
@@ -307918,54 +307522,6 @@ var TableSortLabel = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_2__["forwardRef
 
 /***/ }),
 
-/***/ "lNPj":
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-var _interopRequireDefault = __webpack_require__("jm00");
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.isFlagshipApp = exports.getFlagshipMetadata = exports.FlagshipRoutes = void 0;
-
-var _cozyLogger = _interopRequireDefault(__webpack_require__("rUPj"));
-
-var FlagshipRoutes;
-exports.FlagshipRoutes = FlagshipRoutes;
-
-(function (FlagshipRoutes) {
-  FlagshipRoutes["Home"] = "home";
-  FlagshipRoutes["Cozyapp"] = "cozyapp";
-  FlagshipRoutes["Authenticate"] = "authenticate";
-  FlagshipRoutes["Onboarding"] = "onboarding";
-  FlagshipRoutes["Stack"] = "stack";
-})(FlagshipRoutes || (exports.FlagshipRoutes = FlagshipRoutes = {}));
-
-var getGlobalWindow = function getGlobalWindow() {
-  return typeof window !== 'undefined' ? window : ((0, _cozyLogger.default)('error', "\"window\" is not defined. This means that getGlobalWindow() shouldn't have been called and investigation should be done to prevent this call"), undefined);
-};
-
-var getFlagshipMetadata = function getFlagshipMetadata() {
-  var _getGlobalWindow$cozy, _getGlobalWindow, _getGlobalWindow$cozy2;
-
-  return (_getGlobalWindow$cozy = (_getGlobalWindow = getGlobalWindow()) === null || _getGlobalWindow === void 0 ? void 0 : (_getGlobalWindow$cozy2 = _getGlobalWindow.cozy) === null || _getGlobalWindow$cozy2 === void 0 ? void 0 : _getGlobalWindow$cozy2.flagship) !== null && _getGlobalWindow$cozy !== void 0 ? _getGlobalWindow$cozy : {};
-};
-
-exports.getFlagshipMetadata = getFlagshipMetadata;
-
-var isFlagshipApp = function isFlagshipApp() {
-  var _getGlobalWindow2, _getGlobalWindow2$coz;
-
-  return ((_getGlobalWindow2 = getGlobalWindow()) === null || _getGlobalWindow2 === void 0 ? void 0 : (_getGlobalWindow2$coz = _getGlobalWindow2.cozy) === null || _getGlobalWindow2$coz === void 0 ? void 0 : _getGlobalWindow2$coz.flagship) !== undefined;
-};
-
-exports.isFlagshipApp = isFlagshipApp;
-
-/***/ }),
-
 /***/ "lPmf":
 /***/ (function(module, exports, __webpack_require__) {
 
@@ -312693,64 +312249,6 @@ function getLocationHref() {
 
 /***/ }),
 
-/***/ "m6Tf":
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-var _interopRequireDefault = __webpack_require__("jm00");
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.getDeviceName = void 0;
-
-var _capitalize = _interopRequireDefault(__webpack_require__("13ub"));
-
-var _cordova = __webpack_require__("dlno");
-
-var _plugins = __webpack_require__("QJIl");
-
-var _platform = __webpack_require__("AzAX");
-
-var DEFAULT_DEVICE = 'Device';
-
-// device
-var getAppleModel = function getAppleModel(identifier) {
-  var devices = ['iPhone', 'iPad', 'Watch', 'AppleTV'];
-
-  for (var _i = 0, _devices = devices; _i < _devices.length; _i++) {
-    var _device = _devices[_i];
-
-    if (identifier.match(new RegExp(_device))) {
-      return _device;
-    }
-  }
-
-  return DEFAULT_DEVICE;
-};
-
-var getDeviceName = function getDeviceName() {
-  if (!(0, _plugins.hasDevicePlugin)()) {
-    if ((0, _cordova.isCordova)()) {
-      console.warn('You should install `cordova-plugin-device`.'); // eslint-disable-line no-console
-    }
-
-    return DEFAULT_DEVICE;
-  }
-
-  var _window$device = window.device,
-      manufacturer = _window$device.manufacturer,
-      originalModel = _window$device.model;
-  var model = (0, _platform.isIOSApp)() ? getAppleModel(originalModel) : originalModel;
-  return "".concat((0, _capitalize.default)(manufacturer), " ").concat(model);
-};
-
-exports.getDeviceName = getDeviceName;
-
-/***/ }),
-
 /***/ "m7Is":
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
@@ -374516,10 +374014,10 @@ var _createClass2 = _interopRequireDefault(__webpack_require__("Zvb3"));
 
 var _microee = _interopRequireDefault(__webpack_require__("GIvT"));
 
-var _logger = _interopRequireDefault(__webpack_require__("o2qs"));
-
 var _config = __webpack_require__("+RmU");
 
+var _logger = _interopRequireDefault(__webpack_require__("o2qs"));
+
 /**
  * This class creates an helper for processes that need
  * a retry mechanism with some wait between failed attempts.
@@ -374532,6 +374030,7 @@ var RetryManager = /*#__PURE__*/function () {
    * @param {number} baseWaitAfterFirstFailure - how much time in ms should we wait after the first failure?
    * @param {number} timeBeforeSuccessful - how much time should  we wait without error to acknowledge a success?
    * @param {number} raiseErrorAfterAttempts - after how many failed attempts should we raise an error?
+   * @param {object} logger A custom logger
    */
   function RetryManager() {
     var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
@@ -374542,9 +374041,12 @@ var RetryManager = /*#__PURE__*/function () {
         _ref$timeBeforeSucces = _ref.timeBeforeSuccessful,
         timeBeforeSuccessful = _ref$timeBeforeSucces === void 0 ? _config.timeBeforeSuccessful : _ref$timeBeforeSucces,
         _ref$raiseErrorAfterA = _ref.raiseErrorAfterAttempts,
-        raiseErrorAfterAttempts = _ref$raiseErrorAfterA === void 0 ? _config.raiseErrorAfterAttempts : _ref$raiseErrorAfterA;
+        raiseErrorAfterAttempts = _ref$raiseErrorAfterA === void 0 ? _config.raiseErrorAfterAttempts : _ref$raiseErrorAfterA,
+        _ref$logger = _ref.logger,
+        logger = _ref$logger === void 0 ? _logger.default : _ref$logger;
 
     (0, _classCallCheck2.default)(this, RetryManager);
+    this.logger = logger;
     this.reset();
     this.onSuccess = this.onSuccess.bind(this);
     this.onFailure = this.onFailure.bind(this);
@@ -374639,8 +374141,7 @@ var RetryManager = /*#__PURE__*/function () {
   }, {
     key: "onFailure",
     value: function onFailure(error) {
-      _logger.default.debug('failure, increase the failure counter of the retry manager');
-
+      this.logger.debug('failure, increase the failure counter of the retry manager');
       this.clearSuccessTimer();
       this.increaseFailureCounter();
       this.emit('failure', error);
@@ -374664,8 +374165,7 @@ var RetryManager = /*#__PURE__*/function () {
   }, {
     key: "reset",
     value: function reset() {
-      _logger.default.debug('reset the retry manager');
-
+      this.logger.debug('reset the retry manager');
       this.clearSuccessTimer();
       this.retries = 0;
       this.wait = 0;
@@ -374706,7 +374206,7 @@ var RetryManager = /*#__PURE__*/function () {
           while (1) {
             switch (_context.prev = _context.next) {
               case 0:
-                _logger.default.debug('waitBeforeNextAttempt', this.wait);
+                this.logger.debug('waitBeforeNextAttempt', this.wait);
 
                 if (!this.wait) {
                   _context.next = 5;
@@ -381062,13 +380562,13 @@ var _createClass2 = _interopRequireDefault(__webpack_require__("Zvb3"));
 
 var _isEqual = _interopRequireDefault(__webpack_require__("+KM7"));
 
-var _uniqWith = _interopRequireDefault(__webpack_require__("c20g"));
-
 var _pick = _interopRequireDefault(__webpack_require__("LF8A"));
 
+var _pullAt = _interopRequireDefault(__webpack_require__("tW+3"));
+
 var _remove2 = _interopRequireDefault(__webpack_require__("Jb16"));
 
-var _pullAt = _interopRequireDefault(__webpack_require__("tW+3"));
+var _uniqWith = _interopRequireDefault(__webpack_require__("c20g"));
 
 var _config = __webpack_require__("+RmU");
 
@@ -381092,8 +380592,10 @@ var _logger = _interopRequireDefault(__webpack_require__("o2qs"));
  */
 var SubscriptionList = /*#__PURE__*/function () {
   function SubscriptionList() {
+    var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
     (0, _classCallCheck2.default)(this, SubscriptionList);
     this.subscriptions = [];
+    this.logger = options.logger || _logger.default;
   }
   /**
    * Adds a subscription to the list
@@ -381112,7 +380614,9 @@ var SubscriptionList = /*#__PURE__*/function () {
       });
 
       if (found) {
-        _config.onDoubleSubscriptions && (0, _config.onDoubleSubscriptions)(subscription);
+        _config.onDoubleSubscriptions && (0, _config.onDoubleSubscriptions)(subscription, {
+          logger: this.logger
+        });
         if (!_config.allowDoubleSubscriptions) return;
       }
 
@@ -381147,7 +380651,7 @@ var SubscriptionList = /*#__PURE__*/function () {
         if (removed && removed.length > 0) return;
       }
 
-      _logger.default.warn('Trying to unsubscribe to an unknown subscription', subscription);
+      this.logger.warn('Trying to unsubscribe to an unknown subscription', subscription);
     }
     /**
      * Get all subscriptions
@@ -381259,8 +380763,7 @@ var SubscriptionList = /*#__PURE__*/function () {
     key: "normalize",
     value: function normalize(sub) {
       function error(msg) {
-        _logger.default.error(msg);
-
+        this.logger.error(msg);
         throw new Error(msg);
       }
 
@@ -393047,12 +392550,13 @@ var _interopRequireDefault = __webpack_require__("jm00");
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.doctype = void 0;
+exports.createWebSocket = createWebSocket;
 exports.getCozyClientFromOptions = getCozyClientFromOptions;
 exports.getToken = getToken;
 exports.getUrl = getUrl;
 exports.hasBrowserContext = void 0;
 exports.isOnline = isOnline;
+exports.protocol = void 0;
 
 var _has = _interopRequireDefault(__webpack_require__("sFVN"));
 
@@ -393064,19 +392568,20 @@ var _logger = _interopRequireDefault(__webpack_require__("o2qs"));
  */
 var hasBrowserContext = typeof window !== 'undefined';
 /**
- * The cozy Realtime doctype
+ * The cozy Realtime protocol name
+ * See https://github.com/cozy/cozy-stack/blob/master/docs/realtime.md
  * @type {string}
  */
 
 exports.hasBrowserContext = hasBrowserContext;
-var doctype = 'io.cozy.websocket';
+var protocol = 'io.cozy.websocket';
 /**
  * Returns if the navigator is online
  *
  * @returns {boolean} true if online or unknown
  */
 
-exports.doctype = doctype;
+exports.protocol = protocol;
 
 function isOnline() {
   var hasOnline = (0, _has.default)(global, 'navigator.onLine');
@@ -393146,16 +392651,22 @@ function getToken(client) {
 
 function getCozyClientFromOptions(_ref) {
   var cozyClient = _ref.cozyClient,
-      client = _ref.client;
+      client = _ref.client,
+      _ref$logger = _ref.logger,
+      logger = _ref$logger === void 0 ? _logger.default : _ref$logger;
 
   if (cozyClient) {
-    _logger.default.warn('Passing a `cozyClient` parameter is deprecated, please use `client` instead');
+    logger.warn('Passing a `cozyClient` parameter is deprecated, please use `client` instead');
   } else if (!client) {
-    _logger.default.warn('Realtime must be initialized with a client. Ex: `new Realtime({ client })`');
+    logger.warn('Realtime must be initialized with a client. Ex: `new Realtime({ client })`');
   }
 
   return client || cozyClient;
 }
+
+function createWebSocket(url, protocol) {
+  return new WebSocket(url, protocol);
+}
 /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__("aY11")))
 
 /***/ }),
@@ -406044,91 +405555,6 @@ function createSvgIcon(path, displayName) {
 
 /***/ }),
 
-/***/ "tDKi":
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-var _interopRequireDefault = __webpack_require__("jm00");
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.nativeLinkOpen = void 0;
-
-var _regenerator = _interopRequireDefault(__webpack_require__("hJxD"));
-
-var _asyncToGenerator2 = _interopRequireDefault(__webpack_require__("HZZ/"));
-
-var _plugins = __webpack_require__("QJIl");
-
-var nativeLinkOpen = /*#__PURE__*/function () {
-  var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(_ref) {
-    var url, target, options;
-    return _regenerator.default.wrap(function _callee$(_context) {
-      while (1) {
-        switch (_context.prev = _context.next) {
-          case 0:
-            url = _ref.url;
-            _context.next = 3;
-            return (0, _plugins.hasSafariPlugin)();
-
-          case 3:
-            _context.t0 = _context.sent;
-
-            if (!_context.t0) {
-              _context.next = 6;
-              break;
-            }
-
-            _context.t0 = window.SafariViewController;
-
-          case 6:
-            if (!_context.t0) {
-              _context.next = 10;
-              break;
-            }
-
-            window.SafariViewController.show({
-              url: url,
-              transition: 'curl'
-            }, function (result) {
-              if (result.event === 'closed') {
-                window.SafariViewController.hide();
-              }
-            }, function () {
-              window.SafariViewController.hide();
-            });
-            _context.next = 11;
-            break;
-
-          case 10:
-            if ((0, _plugins.hasInAppBrowserPlugin)()) {
-              target = '_blank';
-              options = 'clearcache=yes,zoom=no';
-              window.cordova.InAppBrowser.open(url, target, options);
-            } else {
-              window.location = url;
-            }
-
-          case 11:
-          case "end":
-            return _context.stop();
-        }
-      }
-    }, _callee);
-  }));
-
-  return function nativeLinkOpen(_x) {
-    return _ref2.apply(this, arguments);
-  };
-}();
-
-exports.nativeLinkOpen = nativeLinkOpen;
-
-/***/ }),
-
 /***/ "tEOf":
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
@@ -442923,160 +442349,6 @@ module.exports = function getPublicSuffix(rules, hostname) {
 };
 
 
-/***/ }),
-
-/***/ "yL+W":
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-var _interopRequireDefault = __webpack_require__("jm00");
-
-Object.defineProperty(exports, "__esModule", {
-  value: true
-});
-exports.startApp = exports.default = exports.checkApp = void 0;
-
-var _regenerator = _interopRequireDefault(__webpack_require__("hJxD"));
-
-var _asyncToGenerator2 = _interopRequireDefault(__webpack_require__("HZZ/"));
-
-var _platform = __webpack_require__("AzAX");
-
-var cordovaPluginIsInstalled = function cordovaPluginIsInstalled() {
-  return window.startApp;
-};
-
-/**
- * Normalize startApp params for Android and iOS
- */
-var getParams = function getParams(_ref) {
-  var appId = _ref.appId,
-      uri = _ref.uri;
-
-  if ((0, _platform.isAndroidApp)()) {
-    return {
-      package: appId
-    };
-  } else {
-    return uri;
-  }
-};
-
-var exported = {};
-/**
- * Start an application if it is installed on the phone
- * @returns Promise - False if the application was not able to be started
- */
-
-var startApp = exported.startApp = /*#__PURE__*/function () {
-  var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(appInfo) {
-    var startAppPlugin, isAppInstalled, params;
-    return _regenerator.default.wrap(function _callee$(_context) {
-      while (1) {
-        switch (_context.prev = _context.next) {
-          case 0:
-            startAppPlugin = window.startApp;
-            _context.next = 3;
-            return exported.checkApp(appInfo);
-
-          case 3:
-            isAppInstalled = _context.sent;
-
-            if (!isAppInstalled) {
-              _context.next = 9;
-              break;
-            }
-
-            params = getParams(appInfo);
-            return _context.abrupt("return", new Promise(function (resolve, reject) {
-              if (!cordovaPluginIsInstalled()) {
-                reject(new Error("Cordova plugin 'com.lampa.startapp' is not installed. This plugin is needed to start a native app. Required by cozy-bar"));
-                return;
-              }
-
-              startAppPlugin.set(params).start(resolve, reject);
-            }));
-
-          case 9:
-            return _context.abrupt("return", false);
-
-          case 10:
-          case "end":
-            return _context.stop();
-        }
-      }
-    }, _callee);
-  }));
-
-  return function (_x) {
-    return _ref2.apply(this, arguments);
-  };
-}();
-/**
- * Check that an application is installed on the phone
- * @returns Promise - Promise containing information on the application
- *
- * @example
- * > checkApp({ appId: 'io.cozy.drive.mobile', uri: 'cozydrive://' })
- * Promise.resolve({
- *  versionName: "0.9.2",
- *  packageName: "io.cozy.drive.mobile",
- *  versionCode: 902,
- *  applicationInfo: "ApplicationInfo{70aa0ef io.cozy.drive.mobile}"
- * })
- */
-
-
-exports.startApp = startApp;
-
-var checkApp = exported.checkApp = /*#__PURE__*/function () {
-  var _ref3 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(appInfo) {
-    var startAppPlugin, params;
-    return _regenerator.default.wrap(function _callee2$(_context2) {
-      while (1) {
-        switch (_context2.prev = _context2.next) {
-          case 0:
-            startAppPlugin = window.startApp;
-            params = getParams(appInfo);
-            return _context2.abrupt("return", new Promise(function (resolve, reject) {
-              if (!cordovaPluginIsInstalled()) {
-                reject(new Error("Cordova plugin 'com.lampa.startapp' is not installed."));
-                return;
-              }
-
-              startAppPlugin.set(params).check(function (infos) {
-                return resolve(infos === 'OK' ? true : infos);
-              }, function (error) {
-                if (error === false || error.indexOf('NameNotFoundException') === 0) {
-                  // Plugin returns an error 'NameNotFoundException' on Android and
-                  // false on iOS when an application is not found.
-                  // We prefer to always return false
-                  resolve(false);
-                } else {
-                  reject(error);
-                }
-              });
-            }));
-
-          case 3:
-          case "end":
-            return _context2.stop();
-        }
-      }
-    }, _callee2);
-  }));
-
-  return function (_x2) {
-    return _ref3.apply(this, arguments);
-  };
-}();
-
-exports.checkApp = checkApp;
-var _default = exported;
-exports.default = _default;
-
 /***/ }),
 
 /***/ "yLPM":