cozy-harvest-lib 9.7.3 → 9.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,44 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [9.9.0](https://github.com/cozy/cozy-libs/compare/cozy-harvest-lib@9.8.1...cozy-harvest-lib@9.9.0) (2022-06-13)
7
+
8
+
9
+ ### Features
10
+
11
+ * Add session code inAppBrowser url ([e2037ef](https://github.com/cozy/cozy-libs/commit/e2037efabd73d93106d2f88bb122f75e783afb6e))
12
+
13
+
14
+
15
+
16
+
17
+ ## [9.8.1](https://github.com/cozy/cozy-libs/compare/cozy-harvest-lib@9.8.0...cozy-harvest-lib@9.8.1) (2022-06-12)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * Update french locale ([902ae96](https://github.com/cozy/cozy-libs/commit/902ae967a14c106c7cc3adc0543d479d94e2f07e))
23
+
24
+
25
+
26
+
27
+
28
+ # [9.8.0](https://github.com/cozy/cozy-libs/compare/cozy-harvest-lib@9.7.3...cozy-harvest-lib@9.8.0) (2022-06-09)
29
+
30
+
31
+ ### Bug Fixes
32
+
33
+ * Small fixes after review ([d4f50ba](https://github.com/cozy/cozy-libs/commit/d4f50ba114cd4f8d43fac8545f6356c380edd262))
34
+
35
+
36
+ ### Features
37
+
38
+ * Add BI webview reconnection ([d0f9c9e](https://github.com/cozy/cozy-libs/commit/d0f9c9e556d847e072063eb877d5d4e57ee8cca0))
39
+
40
+
41
+
42
+
43
+
6
44
  ## [9.7.3](https://github.com/cozy/cozy-libs/compare/cozy-harvest-lib@9.7.2...cozy-harvest-lib@9.7.3) (2022-06-08)
7
45
 
8
46
 
@@ -102,7 +102,10 @@ var DumbEditAccountModal = withRouter(function (_ref) {
102
102
  onSuccess: redirectToAccount,
103
103
  showError: true,
104
104
  onVaultDismiss: redirectToAccount,
105
- fieldOptions: fieldOptions
105
+ fieldOptions: fieldOptions,
106
+ reconnect: fromReconnect
107
+ }), /*#__PURE__*/React.createElement("div", {
108
+ className: "u-mb-2"
106
109
  })));
107
110
  });
108
111
  export var EditAccountModal = /*#__PURE__*/function (_Component) {
@@ -16,36 +16,56 @@ var InAppBrowser = function InAppBrowser(_ref) {
16
16
 
17
17
  function _insideEffect() {
18
18
  _insideEffect = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
19
- var result;
19
+ var sessionCode, iabUrl, result;
20
20
  return _regeneratorRuntime.wrap(function _callee$(_context) {
21
21
  while (1) {
22
22
  switch (_context.prev = _context.next) {
23
23
  case 0:
24
24
  if (!webviewIntent) {
25
- _context.next = 5;
25
+ _context.next = 18;
26
26
  break;
27
27
  }
28
28
 
29
- _context.next = 3;
29
+ _context.prev = 1;
30
+ _context.next = 4;
31
+ return webviewIntent.call('fetchSessionCode');
32
+
33
+ case 4:
34
+ sessionCode = _context.sent;
35
+ logger.debug('got session code', sessionCode);
36
+ iabUrl = new URL(url);
37
+ iabUrl.searchParams.append('session_code', sessionCode);
38
+ _context.next = 10;
30
39
  return webviewIntent.call('showInAppBrowser', {
31
- url: url
40
+ url: iabUrl.toString()
32
41
  });
33
42
 
34
- case 3:
43
+ case 10:
35
44
  result = _context.sent;
36
45
 
37
- if ((result === null || result === void 0 ? void 0 : result.type) === 'cancel' && onClose) {
38
- onClose();
39
- } else if ((result === null || result === void 0 ? void 0 : result.type) !== 'dismiss') {
46
+ if ((result === null || result === void 0 ? void 0 : result.type) !== 'dismiss' && (result === null || result === void 0 ? void 0 : result.type) !== 'cancel') {
40
47
  logger.error('Unexpected InAppBrowser result', result);
41
48
  }
42
49
 
43
- case 5:
50
+ _context.next = 17;
51
+ break;
52
+
53
+ case 14:
54
+ _context.prev = 14;
55
+ _context.t0 = _context["catch"](1);
56
+ logger.error('unexpected fetchSessionCode result', _context.t0);
57
+
58
+ case 17:
59
+ if (onClose) {
60
+ onClose();
61
+ }
62
+
63
+ case 18:
44
64
  case "end":
45
65
  return _context.stop();
46
66
  }
47
67
  }
48
- }, _callee);
68
+ }, _callee, null, [[1, 14]]);
49
69
  }));
50
70
  return _insideEffect.apply(this, arguments);
51
71
  }
@@ -5,7 +5,7 @@ import { render, waitFor } from '@testing-library/react';
5
5
  import InAppBrowser from './InAppBrowser';
6
6
  import { WebviewIntentProvider } from 'cozy-intent';
7
7
  describe('InAppBrowser', function () {
8
- it('should call showInAppBrowser', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
8
+ it('should call fetchSessionCode and showInAppBrowser', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
9
9
  var url, intentCall, webviewService;
10
10
  return _regeneratorRuntime.wrap(function _callee$(_context) {
11
11
  while (1) {
@@ -16,7 +16,7 @@ describe('InAppBrowser', function () {
16
16
  webviewService = {
17
17
  call: intentCall
18
18
  };
19
- intentCall.mockResolvedValue({
19
+ intentCall.mockResolvedValueOnce('sessioncode').mockResolvedValueOnce({
20
20
  type: 'dismiss'
21
21
  });
22
22
  render( /*#__PURE__*/React.createElement(WebviewIntentProvider, {
@@ -24,11 +24,18 @@ describe('InAppBrowser', function () {
24
24
  }, /*#__PURE__*/React.createElement(InAppBrowser, {
25
25
  url: url
26
26
  })));
27
- expect(webviewService.call).toHaveBeenNthCalledWith(1, 'showInAppBrowser', {
28
- url: url
27
+ _context.next = 7;
28
+ return waitFor(function () {
29
+ return expect(webviewService.call).toHaveBeenCalled();
29
30
  });
30
31
 
31
- case 6:
32
+ case 7:
33
+ expect(webviewService.call).toHaveBeenNthCalledWith(1, 'fetchSessionCode');
34
+ expect(webviewService.call).toHaveBeenNthCalledWith(2, 'showInAppBrowser', {
35
+ url: url + '/?session_code=sessioncode'
36
+ });
37
+
38
+ case 9:
32
39
  case "end":
33
40
  return _context.stop();
34
41
  }
@@ -38,7 +38,6 @@ export var OAuthForm = /*#__PURE__*/function (_PureComponent) {
38
38
  _this.handleOAuthCancel = _this.handleOAuthCancel.bind(_assertThisInitialized(_this));
39
39
  _this.handleExtraParams = _this.handleExtraParams.bind(_assertThisInitialized(_this));
40
40
  _this.state = {
41
- initialValues: null,
42
41
  showingOAuthModal: false
43
42
  };
44
43
  return _this;
@@ -52,9 +51,6 @@ export var OAuthForm = /*#__PURE__*/function (_PureComponent) {
52
51
  konnector = _this$props.konnector,
53
52
  flow = _this$props.flow,
54
53
  client = _this$props.client;
55
- this.setState({
56
- initialValues: account ? account.oauth : null
57
- });
58
54
  var konnectorPolicy = findKonnectorPolicy(konnector);
59
55
 
60
56
  if (konnectorPolicy.fetchExtraOAuthUrlParams) {
@@ -114,25 +110,29 @@ export var OAuthForm = /*#__PURE__*/function (_PureComponent) {
114
110
  var _this$props2 = this.props,
115
111
  konnector = _this$props2.konnector,
116
112
  t = _this$props2.t,
117
- flowState = _this$props2.flowState;
113
+ flowState = _this$props2.flowState,
114
+ reconnect = _this$props2.reconnect,
115
+ account = _this$props2.account;
118
116
  var _this$state = this.state,
119
- initialValues = _this$state.initialValues,
120
117
  showOAuthWindow = _this$state.showOAuthWindow,
121
118
  needExtraParams = _this$state.needExtraParams,
122
119
  extraParams = _this$state.extraParams;
123
120
  var isBusy = showOAuthWindow === true || flowState.running || needExtraParams && !extraParams;
124
- return initialValues ? null : /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, {
121
+ var buttonLabel = reconnect ? 'oauth.reconnect.label' : 'oauth.connect.label';
122
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, {
125
123
  className: "u-mt-1",
126
124
  busy: isBusy,
127
125
  disabled: isBusy,
128
126
  extension: "full",
129
- label: t('oauth.connect.label'),
127
+ label: t(buttonLabel),
130
128
  onClick: this.handleConnect
131
129
  }), showOAuthWindow && /*#__PURE__*/React.createElement(OAuthWindow, {
132
130
  extraParams: extraParams,
133
131
  konnector: konnector,
132
+ reconnect: reconnect,
134
133
  onSuccess: this.handleAccountId,
135
- onCancel: this.handleOAuthCancel
134
+ onCancel: this.handleOAuthCancel,
135
+ account: account
136
136
  }));
137
137
  }
138
138
  }]);
@@ -150,6 +150,9 @@ OAuthForm.propTypes = {
150
150
  onSuccess: PropTypes.func,
151
151
 
152
152
  /** Translation function */
153
- t: PropTypes.func.isRequired
153
+ t: PropTypes.func.isRequired,
154
+
155
+ /** Is it a reconnection or not */
156
+ reconnect: PropTypes.bool
154
157
  };
155
158
  export default compose(translate(), withConnectionFlow())(OAuthForm);
@@ -30,7 +30,7 @@ describe('OAuthForm', function () {
30
30
  })).getElement();
31
31
  expect(component).toMatchSnapshot();
32
32
  });
33
- it('should not render button when update', function () {
33
+ it('should render reconnect button when updating an account', function () {
34
34
  var component = shallow( /*#__PURE__*/React.createElement(OAuthForm, {
35
35
  flowState: {},
36
36
  account: {
@@ -39,9 +39,10 @@ describe('OAuthForm', function () {
39
39
  }
40
40
  },
41
41
  konnector: fixtures.konnector,
42
+ reconnect: true,
42
43
  t: t
43
44
  })).getElement();
44
- expect(component).toBeNull();
45
+ expect(component).toMatchSnapshot();
45
46
  });
46
47
  it('should call policy fetchExtraOAuthUrlParams with proper params', function () {
47
48
  shallow( /*#__PURE__*/React.createElement(OAuthForm, {
@@ -65,13 +65,15 @@ export var OAuthWindow = /*#__PURE__*/function (_PureComponent) {
65
65
  client = _this$props.client,
66
66
  konnector = _this$props.konnector,
67
67
  redirectSlug = _this$props.redirectSlug,
68
- extraParams = _this$props.extraParams;
68
+ extraParams = _this$props.extraParams,
69
+ reconnect = _this$props.reconnect,
70
+ account = _this$props.account;
69
71
  this.realtime = new CozyRealtime({
70
72
  client: client
71
73
  });
72
74
  this.realtime.subscribe('notified', 'io.cozy.accounts', OAUTH_REALTIME_CHANNEL, this.handleMessage);
73
75
 
74
- var _prepareOAuth = prepareOAuth(client, konnector, redirectSlug, extraParams),
76
+ var _prepareOAuth = prepareOAuth(client, konnector, redirectSlug, extraParams, reconnect, account),
75
77
  oAuthStateKey = _prepareOAuth.oAuthStateKey,
76
78
  oAuthUrl = _prepareOAuth.oAuthUrl;
77
79
 
@@ -210,6 +212,12 @@ OAuthWindow.propTypes = {
210
212
  onCancel: PropTypes.func,
211
213
 
212
214
  /** The app we want to redirect the user on, after the OAuth flow. It used by the stack */
213
- redirectSlug: PropTypes.string
215
+ redirectSlug: PropTypes.string,
216
+
217
+ /** Is it a reconnection or not */
218
+ reconnect: PropTypes.bool,
219
+
220
+ /** Existing account */
221
+ account: PropTypes.object
214
222
  };
215
223
  export default translate()(withClient(OAuthWindow));
@@ -507,7 +507,8 @@ export var DumbTriggerManager = /*#__PURE__*/function (_Component) {
507
507
  flow = _this$props5.flow,
508
508
  flowState = _this$props5.flowState,
509
509
  client = _this$props5.client,
510
- OAuthFormWrapperComp = _this$props5.OAuthFormWrapperComp;
510
+ OAuthFormWrapperComp = _this$props5.OAuthFormWrapperComp,
511
+ reconnect = _this$props5.reconnect;
511
512
  var submitting = flowState.running;
512
513
  var _this$state = this.state,
513
514
  account = _this$state.account,
@@ -527,6 +528,7 @@ export var DumbTriggerManager = /*#__PURE__*/function (_Component) {
527
528
  client: client,
528
529
  flow: flow,
529
530
  account: account,
531
+ reconnect: reconnect,
530
532
  konnector: konnector,
531
533
  onSuccess: this.handleOAuthAccountId
532
534
  }));
@@ -617,7 +619,10 @@ DumbTriggerManager.propTypes = {
617
619
  flow: PropTypes.object,
618
620
  flowState: PropTypes.object,
619
621
  // Used to inject a component around OAuthForm, and so customize the UI from the app
620
- OAuthFormWrapperComp: PropTypes.node
622
+ OAuthFormWrapperComp: PropTypes.node,
623
+
624
+ /** Is it a reconnection or not */
625
+ reconnect: PropTypes.bool
621
626
  };
622
627
  var TriggerManager = compose(translate(), withClient, withVaultUnlockContext, withConnectionFlow())(DumbTriggerManager); // TriggerManager is exported wrapped in FlowProvider to avoid breaking changes.
623
628
 
@@ -12,3 +12,16 @@ exports[`OAuthForm should render 1`] = `
12
12
  />
13
13
  </React.Fragment>
14
14
  `;
15
+
16
+ exports[`OAuthForm should render reconnect button when update 1`] = `
17
+ <React.Fragment>
18
+ <DefaultButton
19
+ busy={true}
20
+ className="u-mt-1"
21
+ disabled={true}
22
+ extension="full"
23
+ label="oauth.reconnect.label"
24
+ onClick={[Function]}
25
+ />
26
+ </React.Fragment>
27
+ `;
@@ -1,3 +1,4 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
1
2
  import uuid from 'uuid/v4';
2
3
  import get from 'lodash/get';
3
4
  import CozyClient from 'cozy-client';
@@ -94,6 +95,9 @@ export var handleOAuthResponse = function handleOAuthResponse() {
94
95
  * @param {string} redirectSlug The app we want to redirect the user on after the end of the flow
95
96
  * @param {string} nonce unique nonce string
96
97
  * @param {Object} extraParams some extra parameters to add to the query string
98
+ * @param {Boolean} reconnect Are we trying to reconnect an existing account ?
99
+ * @param {io.cozy.accounts} account targeted account if any
100
+ * @returns {String} final OAuth url string
97
101
  */
98
102
 
99
103
  export var getOAuthUrl = function getOAuthUrl(_ref) {
@@ -104,25 +108,35 @@ export var getOAuthUrl = function getOAuthUrl(_ref) {
104
108
  oAuthConf = _ref$oAuthConf === void 0 ? {} : _ref$oAuthConf,
105
109
  nonce = _ref.nonce,
106
110
  redirectSlug = _ref.redirectSlug,
107
- extraParams = _ref.extraParams;
108
- var oAuthUrl = "".concat(cozyUrl, "/accounts/").concat(accountType, "/start?state=").concat(oAuthStateKey, "&nonce=").concat(nonce);
111
+ extraParams = _ref.extraParams,
112
+ reconnect = _ref.reconnect,
113
+ account = _ref.account;
114
+ var startOrReconnect = reconnect ? 'reconnect' : 'start';
115
+ var accountIdParam = reconnect ? account._id + '/' : '';
116
+ var oAuthUrl = new URL("".concat(cozyUrl, "/accounts/").concat(accountType, "/").concat(accountIdParam).concat(startOrReconnect));
117
+ oAuthUrl.searchParams.set('state', oAuthStateKey);
118
+ oAuthUrl.searchParams.set('nonce', nonce);
109
119
 
110
120
  if (oAuthConf.scope !== undefined && oAuthConf.scope !== null && oAuthConf.scope !== false) {
111
121
  var urlScope = Array.isArray(oAuthConf.scope) ? oAuthConf.scope.join('+') : oAuthConf.scope;
112
- oAuthUrl += "&scope=".concat(urlScope);
122
+ oAuthUrl.searchParams.set('scope', urlScope);
113
123
  }
114
124
 
115
125
  if (redirectSlug) {
116
- oAuthUrl += "&slug=".concat(redirectSlug);
126
+ oAuthUrl.searchParams.set('slug', redirectSlug);
117
127
  }
118
128
 
119
129
  if (extraParams) {
120
- for (var key in extraParams) {
121
- oAuthUrl += "&".concat(key, "=").concat(extraParams[key]);
122
- }
130
+ Object.entries(extraParams).forEach(function (_ref2) {
131
+ var _ref3 = _slicedToArray(_ref2, 2),
132
+ key = _ref3[0],
133
+ value = _ref3[1];
134
+
135
+ return oAuthUrl.searchParams.set(key, value);
136
+ });
123
137
  }
124
138
 
125
- return oAuthUrl;
139
+ return oAuthUrl.toString();
126
140
  };
127
141
 
128
142
  var getAppSlug = function getAppSlug(client) {
@@ -135,12 +149,16 @@ var getAppSlug = function getAppSlug(client) {
135
149
  * @param {string} domain Cozy domain
136
150
  * @param {Object} konnector
137
151
  * @param {string} redirectSlug The app we want to redirect the user on after the end of the flow
138
- * @return {Object} Object containing: `oAuthUrl` (URL of cozy stack
139
- * OAuth endpoint) and `oAuthStateKey` (localStorage key)
152
+ * @param {Object} extraParams some extra parameters to add to the query string
153
+ * @param {Boolean} reconnect Are we trying to reconnect an existing account ?
154
+ * @param {io.cozy.accounts} account targetted account if any
155
+ * @return {Object} Object containing: `oAuthUrl` (URL of cozy stack OAuth endpoint) and `oAuthStateKey` (localStorage key)
140
156
  */
141
157
 
142
158
 
143
159
  export var prepareOAuth = function prepareOAuth(client, konnector, redirectSlug, extraParams) {
160
+ var reconnect = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
161
+ var account = arguments.length > 5 ? arguments[5] : undefined;
144
162
  var oauth = konnector.oauth;
145
163
  var accountType = konnectors.getAccountType(konnector); // We use localStorage to store the account related data
146
164
  // We will later check in localStorage that the received information is
@@ -159,7 +177,9 @@ export var prepareOAuth = function prepareOAuth(client, konnector, redirectSlug,
159
177
  oAuthConf: oauth,
160
178
  nonce: Date.now(),
161
179
  redirectSlug: redirectSlug || getAppSlug(client),
162
- extraParams: extraParams
180
+ extraParams: extraParams,
181
+ reconnect: reconnect,
182
+ account: account
163
183
  });
164
184
  return {
165
185
  oAuthStateKey: oAuthStateKey,
@@ -53,7 +53,7 @@ describe('Oauth helper', function () {
53
53
  scope: ['thescope', 'thescope2']
54
54
  }
55
55
  }));
56
- expect(url).toEqual('https://cozyurl/accounts/testslug/start?state=statekey&nonce=1234&scope=thescope+thescope2');
56
+ expect(url).toEqual('https://cozyurl/accounts/testslug/start?state=statekey&nonce=1234&scope=thescope%2Bthescope2');
57
57
  });
58
58
  it('should use redirectSlug if present', function () {
59
59
  var url = getOAuthUrl(_objectSpread(_objectSpread({}, defaultConf), {}, {
@@ -72,6 +72,20 @@ describe('Oauth helper', function () {
72
72
  }));
73
73
  expect(url).toEqual('https://cozyurl/accounts/testslug/start?state=statekey&nonce=1234&token=thetoken&id_connector=40');
74
74
  });
75
+ it('should return reconnect url with account id if reconnect', function () {
76
+ var url = getOAuthUrl(_objectSpread(_objectSpread({}, defaultConf), {}, {
77
+ oAuthConf: {},
78
+ account: {
79
+ _id: 'accountid'
80
+ },
81
+ reconnect: true,
82
+ extraParams: {
83
+ code: 'thecode',
84
+ connection_id: 12345
85
+ }
86
+ }));
87
+ expect(url).toEqual('https://cozyurl/accounts/testslug/accountid/reconnect?state=statekey&nonce=1234&code=thecode&connection_id=12345');
88
+ });
75
89
  });
76
90
  describe('handleOAuthResponse', function () {
77
91
  var originalLocation;
@@ -386,6 +386,9 @@
386
386
  }
387
387
  },
388
388
  "oauth": {
389
+ "reconnect": {
390
+ "label": "Reconnect"
391
+ },
389
392
  "connect": {
390
393
  "label": "Connect"
391
394
  },
@@ -386,8 +386,11 @@
386
386
  }
387
387
  },
388
388
  "oauth": {
389
+ "reconnect": {
390
+ "label": "Se reconnecter"
391
+ },
389
392
  "connect": {
390
- "label": "Connecter"
393
+ "label": "Ajouter un compte"
391
394
  },
392
395
  "window": {
393
396
  "title": "OAuth"