diff --git a/index.html b/index.html
index 67b097035a54a020f409262ab90deb30d8fe7b4d..bd2a11246c229faaa7e5501921c832f2f34f1ceb 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.dc6b8a0769b4f8b2dde1.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.70ee95a5783448de3fc2.js"></script><script src="app/ecolyo.554a8422165fe8f24237.js"></script></div></body></html>
\ No newline at end of file
diff --git a/public/ecolyo.95412cfda8c99ee414e9.js b/public/ecolyo.7c6f71167374cc7c0d9f.js
similarity index 99%
rename from public/ecolyo.95412cfda8c99ee414e9.js
rename to public/ecolyo.7c6f71167374cc7c0d9f.js
index 7bd68347c168e92091f81d7fd18fd8e56837aef9..0b59acd228e3004284ddef13733dd992f22daa38 100644
--- a/public/ecolyo.95412cfda8c99ee414e9.js
+++ b/public/ecolyo.7c6f71167374cc7c0d9f.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');
     }
   }
 };
@@ -2388,154 +2391,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":
@@ -16855,6 +16710,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
  *
@@ -16869,10 +16729,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);
@@ -16885,7 +16751,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');
     }
@@ -38069,129 +37937,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) {
 
@@ -40468,80 +40213,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":
@@ -78131,6 +77802,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"));
@@ -78139,17 +77812,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; } } }; }
 
@@ -78157,6 +77834,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
  */
@@ -78164,19 +77846,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);
@@ -78184,7 +77873,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.");
     }
   }
   /**
@@ -78200,8 +77889,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();
@@ -78222,17 +77910,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;
@@ -78262,24 +77948,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;
 
@@ -78359,13 +78041,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;
 
@@ -78376,7 +78064,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');
       }
     }
     /**
@@ -78388,8 +78076,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();
@@ -78417,13 +78104,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);
       }
     }
     /**
@@ -78482,8 +78167,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);
@@ -78496,7 +78180,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) {
@@ -78511,9 +78195,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:
@@ -78694,12 +78378,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);
       }
     }
     /**
@@ -78720,12 +78402,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);
       }
     }
     /**
@@ -78827,11 +78507,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),
@@ -78842,9 +78521,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) {
@@ -78854,7 +78535,7 @@ var CozyRealtime = /*#__PURE__*/function () {
       }
 
       if (event === 'error') {
-        _logger.default.warn('Stack returned an error', payload);
+        this.logger.warn('Stack returned an error', payload);
       }
     }
     /**
@@ -78865,8 +78546,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();
     }
@@ -78891,8 +78571,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();
     }
@@ -78934,7 +78613,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
@@ -79072,8 +78751,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
       });
@@ -79699,60 +79377,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__) {
 
@@ -108577,26 +108201,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":
@@ -128019,54 +127623,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__) {
 
@@ -131725,64 +131281,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__) {
 
@@ -156262,10 +155760,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.
@@ -156278,6 +155776,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] : {},
@@ -156288,9 +155787,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);
@@ -156385,8 +155887,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);
@@ -156410,8 +155911,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;
@@ -156452,7 +155952,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;
@@ -159262,13 +158762,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");
 
@@ -159292,8 +158792,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
@@ -159312,7 +158814,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;
       }
 
@@ -159347,7 +158851,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
@@ -159459,8 +158963,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);
       }
 
@@ -162777,12 +162280,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"));
 
@@ -162794,19 +162298,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');
@@ -162876,16 +162381,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")))
 
 /***/ }),
@@ -166576,91 +166087,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":
@@ -179302,160 +178728,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 f028da791b203a686aef2effc8b659a7ee5fb592..530b48391ad3b6b038f495f1b07be6efca5f9d14 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.95412cfda8c99ee414e9.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.7c6f71167374cc7c0d9f.js"></script></div></body></html>
\ No newline at end of file
diff --git a/vendors/ecolyo.dc6b8a0769b4f8b2dde1.js b/vendors/ecolyo.70ee95a5783448de3fc2.js
similarity index 99%
rename from vendors/ecolyo.dc6b8a0769b4f8b2dde1.js
rename to vendors/ecolyo.70ee95a5783448de3fc2.js
index c7531c956c1f766a83d26cc402e1b8404ddbe38a..410335316dda6676e2654581840b9322ffebbafb 100644
--- a/vendors/ecolyo.dc6b8a0769b4f8b2dde1.js
+++ b/vendors/ecolyo.70ee95a5783448de3fc2.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');
     }
   }
 };
@@ -8675,154 +8678,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":
@@ -34435,6 +34290,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
  *
@@ -34449,10 +34309,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);
@@ -34465,7 +34331,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');
     }
@@ -79806,129 +79674,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) {
 
@@ -83776,80 +83521,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__) {
 
@@ -179934,6 +179605,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"));
@@ -179942,17 +179615,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; } } }; }
 
@@ -179960,6 +179637,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
  */
@@ -179967,19 +179649,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);
@@ -179987,7 +179676,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.");
     }
   }
   /**
@@ -180003,8 +179692,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();
@@ -180025,17 +179713,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;
@@ -180065,24 +179751,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;
 
@@ -180162,13 +179844,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;
 
@@ -180179,7 +179867,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');
       }
     }
     /**
@@ -180191,8 +179879,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();
@@ -180220,13 +179907,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);
       }
     }
     /**
@@ -180285,8 +179970,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);
@@ -180299,7 +179983,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) {
@@ -180314,9 +179998,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:
@@ -180497,12 +180181,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);
       }
     }
     /**
@@ -180523,12 +180205,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);
       }
     }
     /**
@@ -180630,11 +180310,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),
@@ -180645,9 +180324,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) {
@@ -180657,7 +180338,7 @@ var CozyRealtime = /*#__PURE__*/function () {
       }
 
       if (event === 'error') {
-        _logger.default.warn('Stack returned an error', payload);
+        this.logger.warn('Stack returned an error', payload);
       }
     }
     /**
@@ -180668,8 +180349,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();
     }
@@ -180694,8 +180374,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();
     }
@@ -180737,7 +180416,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
@@ -180875,8 +180554,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
       });
@@ -183868,60 +183546,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__) {
 
@@ -260031,26 +259655,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__) {
 
@@ -295103,54 +294707,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__) {
 
@@ -299792,64 +299348,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__) {
 
@@ -360552,10 +360050,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.
@@ -360568,6 +360066,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] : {},
@@ -360578,9 +360077,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);
@@ -360675,8 +360177,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);
@@ -360700,8 +360201,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;
@@ -360742,7 +360242,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;
@@ -366789,13 +366289,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");
 
@@ -366819,8 +366319,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
@@ -366839,7 +366341,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;
       }
 
@@ -366874,7 +366378,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
@@ -366986,8 +366490,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);
       }
 
@@ -378397,12 +377900,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"));
 
@@ -378414,19 +377918,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');
@@ -378496,16 +378001,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")))
 
 /***/ }),
@@ -390442,91 +389953,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__) {
 
@@ -425627,160 +425053,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":