cozy-ui 116.0.0 → 117.0.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
@@ -1,3 +1,17 @@
1
+ # [117.0.0](https://github.com/cozy/cozy-ui/compare/v116.0.0...v117.0.0) (2025-01-13)
2
+
3
+
4
+ ### Features
5
+
6
+ * Remove cordova stuff from AppLinker ([add9fbb](https://github.com/cozy/cozy-ui/commit/add9fbb))
7
+ * Remove deprecated slug prop from AppLinker ([a3d47e0](https://github.com/cozy/cozy-ui/commit/a3d47e0))
8
+
9
+
10
+ ### BREAKING CHANGES
11
+
12
+ * Use app={{ slug: 'drive' }} instead of slug='drive' as
13
+ AppLinker prop. Also, `name` is not passed anymore as render prop.
14
+
1
15
  # [116.0.0](https://github.com/cozy/cozy-ui/compare/v115.1.0...v116.0.0) (2025-01-07)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-ui",
3
- "version": "116.0.0",
3
+ "version": "117.0.0",
4
4
  "description": "Cozy apps UI SDK",
5
5
  "main": "./index.js",
6
6
  "bin": {
@@ -1,15 +1,10 @@
1
1
  Render-props component that provides onClick/href handler to
2
2
  apply to an anchor that needs to open an app.
3
3
 
4
- If the app is known to Cozy (for example Drive or Banks), and
5
- the user has installed it on its device, the native app will
6
- be opened.
7
-
8
4
  The app's manifest can be set in the `app` prop. Then, in a
9
5
  ReactNative environment, the AppLinker will be able to send
10
6
  `openApp` message to the native environment with this `app`
11
- data. Ideally the `mobile` member should be set with all data
12
- needed to open the native app ([more info](https://github.com/cozy/cozy-stack/blob/master/docs/apps.md#mobile))
7
+ data.
13
8
 
14
9
  Handles several cases:
15
10
 
@@ -28,32 +23,9 @@ const app = {
28
23
  };
29
24
 
30
25
  <AppLinker app={app} href='http://dalailama-banks.mycozy.cloud'>{
31
- ({ onClick, href, name }) => (
32
- <a href={href} onClick={onClick}>
33
- Open { name }
34
- </a>
35
- )
36
- }</AppLinker>
37
- ```
38
-
39
- ### Exemple with mobile data
40
-
41
- ```jsx
42
- import AppLinker from 'cozy-ui/transpiled/react/AppLinker';
43
-
44
- const app = {
45
- slug: 'passwords',
46
- mobile: {
47
- schema: 'cozypass://',
48
- id_playstore: 'io.cozy.pass',
49
- id_appstore: 'cozy-pass/id1502262449'
50
- }
51
- };
52
-
53
- <AppLinker app={app} href='http://dalailama-passwords.mycozy.cloud'>{
54
- ({ onClick, href, name }) => (
26
+ ({ onClick, href }) => (
55
27
  <a href={href} onClick={onClick}>
56
- Open { name }
28
+ Open
57
29
  </a>
58
30
  )
59
31
  }</AppLinker>
@@ -1,16 +1,5 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
- exports[`app icon should not crash if no href 1`] = `
4
- <div>
5
- <div>
6
- <a>
7
- Open
8
- Cozy Drive
9
- </a>
10
- </div>
11
- </div>
12
- `;
13
-
14
3
  exports[`app icon should render correctly 1`] = `
15
4
  <div>
16
5
  <div>
@@ -18,7 +7,6 @@ exports[`app icon should render correctly 1`] = `
18
7
  href="https://fake.link"
19
8
  >
20
9
  Open
21
- Cozy Drive
22
10
  </a>
23
11
  </div>
24
12
  </div>
@@ -2,45 +2,25 @@ import PropTypes from 'prop-types'
2
2
  import React from 'react'
3
3
 
4
4
  import { withClient } from 'cozy-client'
5
- import {
6
- checkApp,
7
- startApp,
8
- isMobileApp,
9
- isMobile,
10
- openDeeplinkOrRedirect,
11
- isAndroid,
12
- isFlagshipApp
13
- } from 'cozy-device-helper'
5
+ import { isFlagshipApp } from 'cozy-device-helper'
14
6
  import { WebviewContext } from 'cozy-intent'
15
7
  import logger from 'cozy-logger'
16
8
 
17
- import expiringMemoize from './expiringMemoize'
18
9
  import {
19
10
  generateUniversalLink,
20
11
  generateWebLink,
21
12
  getUniversalLinkDomain
22
13
  } from './native'
23
- import { NATIVE_APP_INFOS } from './native.config'
24
-
25
- const expirationDelay = 10 * 1000
26
- const memoizedCheckApp = expiringMemoize(
27
- appInfo => checkApp(appInfo).catch(() => false),
28
- expirationDelay,
29
- appInfo => appInfo.appId
30
- )
31
14
 
32
15
  export class AppLinker extends React.Component {
33
16
  static contextType = WebviewContext
34
17
 
35
18
  state = {
36
- nativeAppIsAvailable: null,
37
- isFetchingAppInfo: false
19
+ imgRef: null
38
20
  }
39
21
 
40
22
  constructor(props) {
41
23
  super(props)
42
-
43
- this.imgRef = null
44
24
  }
45
25
 
46
26
  setImgRef = img => {
@@ -48,44 +28,14 @@ export class AppLinker extends React.Component {
48
28
  this.setState({ imgRef: this.imgRef })
49
29
  }
50
30
 
51
- componentDidMount() {
52
- if (isMobileApp()) {
53
- this.checkAppAvailability()
54
- }
55
- }
56
-
57
- async checkAppAvailability() {
58
- const slug = AppLinker.getSlug(this.props)
59
- const appInfo = NATIVE_APP_INFOS[slug]
60
- if (appInfo) {
61
- const nativeAppIsAvailable = Boolean(await memoizedCheckApp(appInfo))
62
- this.setState({ nativeAppIsAvailable })
63
- }
64
- }
65
-
66
31
  static getSlug(props) {
67
- if (props.app && props.app.slug) {
68
- return props.app.slug
69
- }
70
-
71
- return props.slug
72
- }
73
-
74
- static deprecateSlug(props) {
75
- if (props.slug) {
76
- console.warn(
77
- `AppLinker's 'slug' prop is deprecated, please use 'app.slug' instead`
78
- )
79
- }
32
+ return props.app.slug
80
33
  }
81
34
 
82
- static getOnClickHref(props, nativeAppIsAvailable, context, imgRef) {
83
- const { app, client, nativePath } = props
84
- const slug = AppLinker.getSlug(props)
35
+ static getOnClickHref(props, context, imgRef) {
36
+ const { app, client } = props
85
37
  let href = props.href
86
38
  let onClick = null
87
- const usingNativeApp = isMobileApp()
88
- const appInfo = NATIVE_APP_INFOS[slug]
89
39
 
90
40
  if (isFlagshipApp()) {
91
41
  const { app: currentApp } = client
@@ -114,59 +64,11 @@ export class AppLinker extends React.Component {
114
64
  }
115
65
  }
116
66
 
117
- if (usingNativeApp) {
118
- if (nativeAppIsAvailable) {
119
- // If we are on the native app and the other native app is available,
120
- // we open the native app
121
- onClick = AppLinker.openNativeFromNative.bind(this, props)
122
- href = '#'
123
- } else {
124
- // If we are on a native app, but the other native app is not available
125
- // we open the web link, this is done by the href prop. We still
126
- // have to call the prop callback
127
- onClick = AppLinker.openWeb.bind(this, props)
128
- }
129
- } else if (isMobile() && appInfo) {
130
- // If we are on the "mobile web version", we try to open the native app
131
- // if it exists with an universal links. If it fails, we redirect to the web
132
- // version of the requested app
133
- // Only on iOS ATM
134
- if (isAndroid()) {
135
- onClick = AppLinker.openNativeFromWeb.bind(this, props)
136
- } else {
137
- // Since generateUniversalLink can rise an error, let's catch it to not crash
138
- // all the page.
139
- try {
140
- href = generateUniversalLink({ slug, nativePath, fallbackUrl: href })
141
- } catch (err) {
142
- console.error(err)
143
- href = '#'
144
- }
145
- }
146
- }
147
-
148
67
  return {
149
68
  href,
150
69
  onClick
151
70
  }
152
71
  }
153
- static openNativeFromWeb(props, ev) {
154
- const { href, nativePath, onAppSwitch } = props
155
- const slug = AppLinker.getSlug(props)
156
- const appInfo = NATIVE_APP_INFOS[slug]
157
-
158
- if (ev) {
159
- ev.preventDefault()
160
- }
161
-
162
- AppLinker.onAppSwitch(onAppSwitch)
163
- openDeeplinkOrRedirect(
164
- appInfo.uri + (nativePath === '/' ? '' : nativePath),
165
- function () {
166
- window.location.href = href
167
- }
168
- )
169
- }
170
72
 
171
73
  static onAppSwitch(onAppSwitchFn) {
172
74
  if (typeof onAppSwitchFn === 'function') {
@@ -174,38 +76,20 @@ export class AppLinker extends React.Component {
174
76
  }
175
77
  }
176
78
 
177
- static openNativeFromNative(props, ev) {
178
- const { onAppSwitch } = props
179
- const slug = AppLinker.getSlug(props)
180
- if (ev) {
181
- ev.preventDefault()
182
- }
183
- const appInfo = NATIVE_APP_INFOS[slug]
184
- AppLinker.onAppSwitch(onAppSwitch)
185
- startApp(appInfo).catch(err => {
186
- console.error('AppLinker: Could not open native app', err)
187
- })
188
- }
189
-
190
79
  static openWeb(props) {
191
80
  AppLinker.onAppSwitch(props.onAppSwitch)
192
81
  }
193
82
 
194
83
  render() {
195
84
  const { children } = this.props
196
- AppLinker.deprecateSlug(this.props)
197
- const slug = AppLinker.getSlug(this.props)
198
- const { nativeAppIsAvailable } = this.state
199
- const appInfo = NATIVE_APP_INFOS[slug]
85
+
200
86
  const { href, onClick } = AppLinker.getOnClickHref(
201
87
  this.props,
202
- nativeAppIsAvailable,
203
88
  this.context,
204
89
  this.state.imgRef
205
90
  )
206
91
 
207
92
  return children({
208
- ...appInfo,
209
93
  iconRef: this.setImgRef,
210
94
  onClick: onClick,
211
95
  href
@@ -213,38 +97,18 @@ export class AppLinker extends React.Component {
213
97
  }
214
98
  }
215
99
 
216
- AppLinker.defaultProps = {
217
- nativePath: '/'
218
- }
219
100
  AppLinker.propTypes = {
220
- /** DEPRECATED: please use app.slug prop */
221
- slug: PropTypes.string,
222
101
  /*
223
102
  Full web url : Used by default on desktop browser
224
103
  Used as a fallback_uri on mobile web
225
104
  */
226
105
  href: PropTypes.string,
227
- /*
228
- Path used for "native link"
229
- */
230
- nativePath: PropTypes.string,
231
106
  onAppSwitch: PropTypes.func,
232
107
  app: PropTypes.shape({
233
108
  // Slug of the app : drive / banks ...
234
- slug: PropTypes.string.isRequired,
235
- // Information about mobile native app
236
- mobile: PropTypes.shape({
237
- schema: PropTypes.string,
238
- id_playstore: PropTypes.string,
239
- id_appstore: PropTypes.string
240
- })
109
+ slug: PropTypes.string.isRequired
241
110
  }).isRequired
242
111
  }
243
112
 
244
113
  export default withClient(AppLinker)
245
- export {
246
- NATIVE_APP_INFOS,
247
- getUniversalLinkDomain,
248
- generateWebLink,
249
- generateUniversalLink
250
- }
114
+ export { getUniversalLinkDomain, generateWebLink, generateUniversalLink }
@@ -2,17 +2,8 @@ import { render } from '@testing-library/react'
2
2
  import userEvent from '@testing-library/user-event'
3
3
  import React from 'react'
4
4
 
5
- import {
6
- isMobileApp,
7
- isMobile,
8
- openDeeplinkOrRedirect,
9
- startApp,
10
- isAndroid,
11
- checkApp
12
- } from 'cozy-device-helper'
13
-
14
5
  import AppLinker from '.'
15
- import { generateUniversalLink } from './native'
6
+
16
7
  jest.useFakeTimers()
17
8
 
18
9
  const setup = ({ app, onAppSwitch }) => {
@@ -32,21 +23,6 @@ const setup = ({ app, onAppSwitch }) => {
32
23
  }
33
24
  }
34
25
 
35
- jest.mock('./native', () => ({
36
- ...jest.requireActual('./native'),
37
- generateUniversalLink: jest.fn()
38
- }))
39
-
40
- jest.mock('cozy-device-helper', () => ({
41
- ...jest.requireActual('cozy-device-helper'),
42
- isMobileApp: jest.fn(),
43
- isMobile: jest.fn(),
44
- openDeeplinkOrRedirect: jest.fn(),
45
- startApp: jest.fn().mockResolvedValue(),
46
- isAndroid: jest.fn(),
47
- checkApp: jest.fn()
48
- }))
49
-
50
26
  const app = {
51
27
  slug: 'drive',
52
28
  name: 'Drive'
@@ -56,16 +32,12 @@ describe('app icon', () => {
56
32
  let spyConsoleError, appSwitchMock
57
33
 
58
34
  beforeEach(() => {
59
- isMobileApp.mockReturnValue(false)
60
35
  spyConsoleError = jest.spyOn(console, 'error')
61
36
  spyConsoleError.mockImplementation(message => {
62
37
  if (message.lastIndexOf('Warning: Failed prop type:') === 0) {
63
38
  throw new Error(message)
64
39
  }
65
40
  })
66
- isMobileApp.mockReturnValue(false)
67
- isMobile.mockReturnValue(false)
68
- isAndroid.mockReturnValue(false)
69
41
  appSwitchMock = jest.fn()
70
42
  })
71
43
 
@@ -80,74 +52,9 @@ describe('app icon', () => {
80
52
  })
81
53
 
82
54
  it('should work for web -> web', async () => {
83
- isMobileApp.mockReturnValue(false)
84
55
  const { container, user } = setup({ app, onAppSwitch: appSwitchMock })
85
56
  const link = container.querySelector('a')
86
57
  await user.click(link)
87
58
  expect(appSwitchMock).not.toHaveBeenCalled()
88
- expect(startApp).not.toHaveBeenCalled()
89
- })
90
-
91
- it('should work for native -> native', async () => {
92
- isMobileApp.mockReturnValue(true)
93
- checkApp.mockResolvedValue(true)
94
- const { container, user } = setup({ app, onAppSwitch: appSwitchMock })
95
- const link = container.querySelector('a')
96
- await user.click(link)
97
- expect(startApp).toHaveBeenCalledWith({
98
- appId: 'io.cozy.drive.mobile',
99
- name: 'Cozy Drive',
100
- uri: 'cozydrive://'
101
- })
102
- expect(appSwitchMock).toHaveBeenCalled()
103
- })
104
-
105
- it('should work for web -> native for Android (custom schema) ', async () => {
106
- isMobile.mockReturnValue(true)
107
- isAndroid.mockResolvedValue(true)
108
- const { container, user } = setup({ app, onAppSwitch: appSwitchMock })
109
- const link = container.querySelector('a')
110
- await user.click(link)
111
- expect(openDeeplinkOrRedirect).toHaveBeenCalledWith(
112
- 'cozydrive://',
113
- expect.any(Function)
114
- )
115
- expect(appSwitchMock).toHaveBeenCalled()
116
- })
117
-
118
- it('should work for web -> native for iOS (universal link)', async () => {
119
- isMobile.mockReturnValue(true)
120
- const { container, user } = setup({ app, onAppSwitch: appSwitchMock })
121
- const link = container.querySelector('a')
122
- await user.click(link)
123
-
124
- expect(generateUniversalLink).toHaveBeenCalled()
125
- })
126
-
127
- it('should work for native -> web', async () => {
128
- isMobileApp.mockReturnValue(true)
129
- const { container, user } = setup({ app, onAppSwitch: appSwitchMock })
130
- const link = container.querySelector('a')
131
- await user.click(link)
132
- expect(appSwitchMock).toHaveBeenCalled()
133
- })
134
-
135
- it('should not crash if no href', () => {
136
- isMobileApp.mockReturnValue(true)
137
- spyConsoleError.mockImplementation(() => {})
138
- const { container } = render(
139
- <AppLinker onAppSwitch={appSwitchMock} app={app}>
140
- {({ onClick, href, name }) => {
141
- return (
142
- <div>
143
- <a href={href} onClick={onClick}>
144
- Open {name}
145
- </a>
146
- </div>
147
- )
148
- }}
149
- </AppLinker>
150
- )
151
- expect(container).toMatchSnapshot()
152
59
  })
153
60
  })
@@ -1,15 +1 @@
1
- import { isAndroidApp } from 'cozy-device-helper'
2
-
3
- export const NATIVE_APP_INFOS = {
4
- drive: {
5
- appId: 'io.cozy.drive.mobile',
6
- uri: 'cozydrive://',
7
- name: 'Cozy Drive'
8
- },
9
- banks: {
10
- appId: isAndroidApp() ? 'io.cozy.banks.mobile' : 'io.cozy.banks',
11
- uri: 'cozybanks://',
12
- name: 'Cozy Banks'
13
- }
14
- }
15
1
  export const UNIVERSAL_LINK_URL = 'https://links.mycozy.cloud'
@@ -1,4 +1,3 @@
1
- import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
1
  import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
3
2
  import _createClass from "@babel/runtime/helpers/createClass";
4
3
  import _assertThisInitialized from "@babel/runtime/helpers/assertThisInitialized";
@@ -11,8 +10,6 @@ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (O
11
10
 
12
11
  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) { _defineProperty(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; }
13
12
 
14
- import _regeneratorRuntime from "@babel/runtime/regenerator";
15
-
16
13
  function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
17
14
 
18
15
  function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
@@ -20,20 +17,10 @@ function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Re
20
17
  import PropTypes from 'prop-types';
21
18
  import React from 'react';
22
19
  import { withClient } from 'cozy-client';
23
- import { checkApp, startApp, isMobileApp, isMobile, openDeeplinkOrRedirect, isAndroid, isFlagshipApp } from 'cozy-device-helper';
20
+ import { isFlagshipApp } from 'cozy-device-helper';
24
21
  import { WebviewContext } from 'cozy-intent';
25
22
  import logger from 'cozy-logger';
26
- import expiringMemoize from "cozy-ui/transpiled/react/AppLinker/expiringMemoize";
27
23
  import { generateUniversalLink, generateWebLink, getUniversalLinkDomain } from "cozy-ui/transpiled/react/AppLinker/native";
28
- import { NATIVE_APP_INFOS } from "cozy-ui/transpiled/react/AppLinker/native.config";
29
- var expirationDelay = 10 * 1000;
30
- var memoizedCheckApp = expiringMemoize(function (appInfo) {
31
- return checkApp(appInfo).catch(function () {
32
- return false;
33
- });
34
- }, expirationDelay, function (appInfo) {
35
- return appInfo.appId;
36
- });
37
24
  export var AppLinker = /*#__PURE__*/function (_React$Component) {
38
25
  _inherits(AppLinker, _React$Component);
39
26
 
@@ -47,8 +34,7 @@ export var AppLinker = /*#__PURE__*/function (_React$Component) {
47
34
  _this = _super.call(this, props);
48
35
 
49
36
  _defineProperty(_assertThisInitialized(_this), "state", {
50
- nativeAppIsAvailable: null,
51
- isFetchingAppInfo: false
37
+ imgRef: null
52
38
  });
53
39
 
54
40
  _defineProperty(_assertThisInitialized(_this), "setImgRef", function (img) {
@@ -59,105 +45,36 @@ export var AppLinker = /*#__PURE__*/function (_React$Component) {
59
45
  });
60
46
  });
61
47
 
62
- _this.imgRef = null;
63
48
  return _this;
64
49
  }
65
50
 
66
51
  _createClass(AppLinker, [{
67
- key: "componentDidMount",
68
- value: function componentDidMount() {
69
- if (isMobileApp()) {
70
- this.checkAppAvailability();
71
- }
72
- }
73
- }, {
74
- key: "checkAppAvailability",
75
- value: function () {
76
- var _checkAppAvailability = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
77
- var slug, appInfo, nativeAppIsAvailable;
78
- return _regeneratorRuntime.wrap(function _callee$(_context) {
79
- while (1) {
80
- switch (_context.prev = _context.next) {
81
- case 0:
82
- slug = AppLinker.getSlug(this.props);
83
- appInfo = NATIVE_APP_INFOS[slug];
84
-
85
- if (!appInfo) {
86
- _context.next = 9;
87
- break;
88
- }
89
-
90
- _context.t0 = Boolean;
91
- _context.next = 6;
92
- return memoizedCheckApp(appInfo);
93
-
94
- case 6:
95
- _context.t1 = _context.sent;
96
- nativeAppIsAvailable = (0, _context.t0)(_context.t1);
97
- this.setState({
98
- nativeAppIsAvailable: nativeAppIsAvailable
99
- });
100
-
101
- case 9:
102
- case "end":
103
- return _context.stop();
104
- }
105
- }
106
- }, _callee, this);
107
- }));
108
-
109
- function checkAppAvailability() {
110
- return _checkAppAvailability.apply(this, arguments);
111
- }
112
-
113
- return checkAppAvailability;
114
- }()
115
- }, {
116
52
  key: "render",
117
53
  value: function render() {
118
54
  var children = this.props.children;
119
- AppLinker.deprecateSlug(this.props);
120
- var slug = AppLinker.getSlug(this.props);
121
- var nativeAppIsAvailable = this.state.nativeAppIsAvailable;
122
- var appInfo = NATIVE_APP_INFOS[slug];
123
55
 
124
- var _AppLinker$getOnClick = AppLinker.getOnClickHref(this.props, nativeAppIsAvailable, this.context, this.state.imgRef),
56
+ var _AppLinker$getOnClick = AppLinker.getOnClickHref(this.props, this.context, this.state.imgRef),
125
57
  href = _AppLinker$getOnClick.href,
126
58
  onClick = _AppLinker$getOnClick.onClick;
127
59
 
128
- return children(_objectSpread(_objectSpread({}, appInfo), {}, {
60
+ return children({
129
61
  iconRef: this.setImgRef,
130
62
  onClick: onClick,
131
63
  href: href
132
- }));
64
+ });
133
65
  }
134
66
  }], [{
135
67
  key: "getSlug",
136
68
  value: function getSlug(props) {
137
- if (props.app && props.app.slug) {
138
- return props.app.slug;
139
- }
140
-
141
- return props.slug;
142
- }
143
- }, {
144
- key: "deprecateSlug",
145
- value: function deprecateSlug(props) {
146
- if (props.slug) {
147
- console.warn("AppLinker's 'slug' prop is deprecated, please use 'app.slug' instead");
148
- }
69
+ return props.app.slug;
149
70
  }
150
71
  }, {
151
72
  key: "getOnClickHref",
152
- value: function getOnClickHref(props, nativeAppIsAvailable, context, imgRef) {
73
+ value: function getOnClickHref(props, context, imgRef) {
153
74
  var app = props.app,
154
- client = props.client,
155
- nativePath = props.nativePath;
156
- var slug = AppLinker.getSlug(props);
75
+ client = props.client;
157
76
  var href = props.href;
158
77
  var onClick = null;
159
- var usingNativeApp = isMobileApp();
160
- var appInfo = NATIVE_APP_INFOS[slug];
161
78
 
162
79
  if (isFlagshipApp()) {
163
80
  var _ref = client ? client.getInstanceOptions() : undefined,
@@ -175,64 +92,11 @@ export var AppLinker = /*#__PURE__*/function (_React$Component) {
175
92
  }
176
93
  }
177
94
 
178
- if (usingNativeApp) {
179
- if (nativeAppIsAvailable) {
180
- // If we are on the native app and the other native app is available,
181
- // we open the native app
182
- onClick = AppLinker.openNativeFromNative.bind(this, props);
183
- href = '#';
184
- } else {
185
- // If we are on a native app, but the other native app is not available
186
- // we open the web link, this is done by the href prop. We still
187
- // have to call the prop callback
188
- onClick = AppLinker.openWeb.bind(this, props);
189
- }
190
- } else if (isMobile() && appInfo) {
191
- // If we are on the "mobile web version", we try to open the native app
192
- // if it exists with an universal links. If it fails, we redirect to the web
193
- // version of the requested app
194
- // Only on iOS ATM
195
- if (isAndroid()) {
196
- onClick = AppLinker.openNativeFromWeb.bind(this, props);
197
- } else {
198
- // Since generateUniversalLink can rise an error, let's catch it to not crash
199
- // all the page.
200
- try {
201
- href = generateUniversalLink({
202
- slug: slug,
203
- nativePath: nativePath,
204
- fallbackUrl: href
205
- });
206
- } catch (err) {
207
- console.error(err);
208
- href = '#';
209
- }
210
- }
211
- }
212
-
213
95
  return {
214
96
  href: href,
215
97
  onClick: onClick
216
98
  };
217
99
  }
218
- }, {
219
- key: "openNativeFromWeb",
220
- value: function openNativeFromWeb(props, ev) {
221
- var href = props.href,
222
- nativePath = props.nativePath,
223
- onAppSwitch = props.onAppSwitch;
224
- var slug = AppLinker.getSlug(props);
225
- var appInfo = NATIVE_APP_INFOS[slug];
226
-
227
- if (ev) {
228
- ev.preventDefault();
229
- }
230
-
231
- AppLinker.onAppSwitch(onAppSwitch);
232
- openDeeplinkOrRedirect(appInfo.uri + (nativePath === '/' ? '' : nativePath), function () {
233
- window.location.href = href;
234
- });
235
- }
236
100
  }, {
237
101
  key: "onAppSwitch",
238
102
  value: function onAppSwitch(onAppSwitchFn) {
@@ -240,22 +104,6 @@ export var AppLinker = /*#__PURE__*/function (_React$Component) {
240
104
  onAppSwitchFn();
241
105
  }
242
106
  }
243
- }, {
244
- key: "openNativeFromNative",
245
- value: function openNativeFromNative(props, ev) {
246
- var onAppSwitch = props.onAppSwitch;
247
- var slug = AppLinker.getSlug(props);
248
-
249
- if (ev) {
250
- ev.preventDefault();
251
- }
252
-
253
- var appInfo = NATIVE_APP_INFOS[slug];
254
- AppLinker.onAppSwitch(onAppSwitch);
255
- startApp(appInfo).catch(function (err) {
256
- console.error('AppLinker: Could not open native app', err);
257
- });
258
- }
259
107
  }, {
260
108
  key: "openWeb",
261
109
  value: function openWeb(props) {
@@ -268,34 +116,17 @@ export var AppLinker = /*#__PURE__*/function (_React$Component) {
268
116
 
269
117
  _defineProperty(AppLinker, "contextType", WebviewContext);
270
118
 
271
- AppLinker.defaultProps = {
272
- nativePath: '/'
273
- };
274
119
  AppLinker.propTypes = {
275
- /** DEPRECATED: please use app.slug prop */
276
- slug: PropTypes.string,
277
-
278
120
  /*
279
121
  Full web url : Used by default on desktop browser
280
122
  Used as a fallback_uri on mobile web
281
123
  */
282
124
  href: PropTypes.string,
283
-
284
- /*
285
- Path used for "native link"
286
- */
287
- nativePath: PropTypes.string,
288
125
  onAppSwitch: PropTypes.func,
289
126
  app: PropTypes.shape({
290
127
  // Slug of the app : drive / banks ...
291
- slug: PropTypes.string.isRequired,
292
- // Information about mobile native app
293
- mobile: PropTypes.shape({
294
- schema: PropTypes.string,
295
- id_playstore: PropTypes.string,
296
- id_appstore: PropTypes.string
297
- })
128
+ slug: PropTypes.string.isRequired
298
129
  }).isRequired
299
130
  };
300
131
  export default withClient(AppLinker);
301
- export { NATIVE_APP_INFOS, getUniversalLinkDomain, generateWebLink, generateUniversalLink };
132
+ export { getUniversalLinkDomain, generateWebLink, generateUniversalLink };
@@ -1,14 +1 @@
1
- import { isAndroidApp } from 'cozy-device-helper';
2
- export var NATIVE_APP_INFOS = {
3
- drive: {
4
- appId: 'io.cozy.drive.mobile',
5
- uri: 'cozydrive://',
6
- name: 'Cozy Drive'
7
- },
8
- banks: {
9
- appId: isAndroidApp() ? 'io.cozy.banks.mobile' : 'io.cozy.banks',
10
- uri: 'cozybanks://',
11
- name: 'Cozy Banks'
12
- }
13
- };
14
1
  export var UNIVERSAL_LINK_URL = 'https://links.mycozy.cloud';
@@ -1,25 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`app icon should not crash if no href 1`] = `
4
- <div>
5
- <div>
6
- <a>
7
- Open
8
- Cozy Drive
9
- </a>
10
- </div>
11
- </div>
12
- `;
13
-
14
- exports[`app icon should render correctly 1`] = `
15
- <div>
16
- <div>
17
- <a
18
- href="https://fake.link"
19
- >
20
- Open
21
- Cozy Drive
22
- </a>
23
- </div>
24
- </div>
25
- `;
@@ -1,13 +0,0 @@
1
- export default function (fn, duration, keyFn) {
2
- const memo = {}
3
- return arg => {
4
- const key = keyFn(arg)
5
- const memoInfo = memo[key]
6
- const uptodate =
7
- memoInfo && memoInfo.result && memoInfo.date - Date.now() < duration
8
- if (!uptodate) {
9
- memo[key] = { result: fn(arg), date: Date.now() }
10
- }
11
- return memo[key].result
12
- }
13
- }
@@ -1,162 +0,0 @@
1
- /**
2
- * Those tests are here to test previous AppLinker implementation base on
3
- * `slug` prop.
4
- * Now that AppLinkers implements `app` prop and uses `app.slug`, the old
5
- * `slug` prop is deprecated
6
- * Those tests should be kept until `slug` prop is completely removed
7
- */
8
-
9
- import { render } from '@testing-library/react'
10
- import userEvent from '@testing-library/user-event'
11
- import React from 'react'
12
-
13
- import {
14
- isMobileApp,
15
- isMobile,
16
- openDeeplinkOrRedirect,
17
- startApp,
18
- isAndroid,
19
- checkApp
20
- } from 'cozy-device-helper'
21
-
22
- import AppLinker from '.'
23
- import { generateUniversalLink } from './native'
24
- jest.useFakeTimers()
25
-
26
- const setup = ({ app, onAppSwitch }) => {
27
- return {
28
- user: userEvent.setup({ delay: null }),
29
- ...render(
30
- <AppLinker
31
- onAppSwitch={onAppSwitch}
32
- slug={app.slug}
33
- href="https://fake.link"
34
- >
35
- {({ onClick, href, name }) => (
36
- <div>
37
- <a href={href} onClick={onClick}>
38
- Open {name}
39
- </a>
40
- </div>
41
- )}
42
- </AppLinker>
43
- )
44
- }
45
- }
46
-
47
- jest.mock('./native', () => ({
48
- ...jest.requireActual('./native'),
49
- generateUniversalLink: jest.fn()
50
- }))
51
-
52
- jest.mock('cozy-device-helper', () => ({
53
- ...jest.requireActual('cozy-device-helper'),
54
- isMobileApp: jest.fn(),
55
- isMobile: jest.fn(),
56
- openDeeplinkOrRedirect: jest.fn(),
57
- startApp: jest.fn().mockResolvedValue(),
58
- isAndroid: jest.fn(),
59
- checkApp: jest.fn()
60
- }))
61
-
62
- const app = {
63
- slug: 'drive',
64
- name: 'Drive'
65
- }
66
-
67
- describe('app icon', () => {
68
- let spyConsoleError, spyConsoleWarn, appSwitchMock
69
-
70
- beforeEach(() => {
71
- isMobileApp.mockReturnValue(false)
72
- spyConsoleError = jest.spyOn(console, 'error')
73
- spyConsoleError.mockImplementation(() => {})
74
- spyConsoleWarn = jest.spyOn(console, 'warn')
75
- spyConsoleWarn.mockImplementation(() => {})
76
- isMobileApp.mockReturnValue(false)
77
- isMobile.mockReturnValue(false)
78
- isAndroid.mockReturnValue(false)
79
- appSwitchMock = jest.fn()
80
- })
81
-
82
- afterEach(() => {
83
- spyConsoleError.mockRestore()
84
- spyConsoleWarn.mockRestore()
85
- jest.restoreAllMocks()
86
- })
87
-
88
- it('should render correctly', () => {
89
- const { container } = setup({ app })
90
- expect(container).toMatchSnapshot()
91
- })
92
-
93
- it('should work for web -> web', async () => {
94
- isMobileApp.mockReturnValue(false)
95
- const { container, user } = setup({ app, onAppSwitch: appSwitchMock })
96
- const link = container.querySelector('a')
97
- await user.click(link)
98
- expect(appSwitchMock).not.toHaveBeenCalled()
99
- expect(startApp).not.toHaveBeenCalled()
100
- })
101
-
102
- it('should work for native -> native', async () => {
103
- isMobileApp.mockReturnValue(true)
104
- checkApp.mockResolvedValue(true)
105
- const { container, user } = setup({ app, onAppSwitch: appSwitchMock })
106
- const link = container.querySelector('a')
107
- await user.click(link)
108
-
109
- expect(startApp).toHaveBeenCalledWith({
110
- appId: 'io.cozy.drive.mobile',
111
- name: 'Cozy Drive',
112
- uri: 'cozydrive://'
113
- })
114
- expect(appSwitchMock).toHaveBeenCalled()
115
- })
116
-
117
- it('should work for web -> native for Android (custom schema)', async () => {
118
- isMobile.mockReturnValue(true)
119
- isAndroid.mockResolvedValue(true)
120
- const { container, user } = setup({ app, onAppSwitch: appSwitchMock })
121
- const link = container.querySelector('a')
122
- await user.click(link)
123
- expect(openDeeplinkOrRedirect).toHaveBeenCalledWith(
124
- 'cozydrive://',
125
- expect.any(Function)
126
- )
127
- expect(appSwitchMock).toHaveBeenCalled()
128
- })
129
-
130
- it('should work for web -> native for iOS (universal link)', async () => {
131
- isMobile.mockReturnValue(true)
132
- const { container, user } = setup({ app, onAppSwitch: appSwitchMock })
133
- const link = container.querySelector('a')
134
- await user.click(link)
135
-
136
- expect(generateUniversalLink).toHaveBeenCalled()
137
- })
138
-
139
- it('should work for native -> web', async () => {
140
- isMobileApp.mockReturnValue(true)
141
- const { container, user } = setup({ app, onAppSwitch: appSwitchMock })
142
- const link = container.querySelector('a')
143
- await user.click(link)
144
- expect(appSwitchMock).toHaveBeenCalled()
145
- })
146
-
147
- it('should not crash if no href', () => {
148
- isMobileApp.mockReturnValue(true)
149
- const { container } = render(
150
- <AppLinker onAppSwitch={appSwitchMock} slug={app.slug}>
151
- {({ onClick, href, name }) => (
152
- <div>
153
- <a href={href} onClick={onClick}>
154
- Open {name}
155
- </a>
156
- </div>
157
- )}
158
- </AppLinker>
159
- )
160
- expect(container).toMatchSnapshot()
161
- })
162
- })
@@ -1,17 +0,0 @@
1
- export default function (fn, duration, keyFn) {
2
- var memo = {};
3
- return function (arg) {
4
- var key = keyFn(arg);
5
- var memoInfo = memo[key];
6
- var uptodate = memoInfo && memoInfo.result && memoInfo.date - Date.now() < duration;
7
-
8
- if (!uptodate) {
9
- memo[key] = {
10
- result: fn(arg),
11
- date: Date.now()
12
- };
13
- }
14
-
15
- return memo[key].result;
16
- };
17
- }