cozy-sharing 33.4.0 → 33.4.1

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,12 @@
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
+ ## [33.4.1](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.4.0...cozy-sharing@33.4.1) (2026-06-15)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **cozy-sharing:** Keep the owner avatar visible in the share modal ([f63ae1d](https://github.com/cozy/cozy-libs/commit/f63ae1de24de5ca7746b0c6da579f52fa5593806))
11
+
6
12
  # [33.4.0](https://github.com/cozy/cozy-libs/compare/cozy-sharing@33.3.4...cozy-sharing@33.4.0) (2026-06-15)
7
13
 
8
14
  ### Bug Fixes
@@ -1,4 +1,5 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
2
3
  import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
3
4
  var _excluded = ["recipient"];
4
5
 
@@ -6,7 +7,7 @@ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (O
6
7
 
7
8
  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; }
8
9
 
9
- import React from 'react';
10
+ import React, { useState } from 'react';
10
11
  import { useClient } from 'cozy-client';
11
12
  import Avatar from 'cozy-ui/transpiled/react/Avatar';
12
13
  import logger from '../../logger';
@@ -16,11 +17,19 @@ var MemberAvatar = function MemberAvatar(_ref) {
16
17
  var recipient = _ref.recipient,
17
18
  rest = _objectWithoutProperties(_ref, _excluded);
18
19
 
19
- var client = useClient(); // There are cases when due to apparent memory leaks, the recipient does not exist
20
+ var client = useClient(); // Remember which image url failed to load so we can fall back to the
21
+ // initials. Storing the url (instead of a boolean) means a new url is
22
+ // retried automatically, without an effect to reset the error state.
23
+
24
+ var _useState = useState(null),
25
+ _useState2 = _slicedToArray(_useState, 2),
26
+ erroredSrc = _useState2[0],
27
+ setErroredSrc = _useState2[1]; // There are cases when due to apparent memory leaks, the recipient does not exist
20
28
  // This will trigger an unhanded error in the Avatar component and crash the app
21
29
  // At the moment, we are not sure where is the root cause of this cascading undefined props
22
30
  // Nevertheless, we can prevent the crash by returning null if the recipient is undefined
23
31
 
32
+
24
33
  if (!recipient) {
25
34
  // eslint-disable-next-line
26
35
  logger.warn('RecipientAvatar: recipient is missing, see props:', _objectSpread({
@@ -40,11 +49,18 @@ var MemberAvatar = function MemberAvatar(_ref) {
40
49
  */
41
50
 
42
51
 
43
- var image = recipient.avatarPath && recipient.status ? "".concat(client.options.uri).concat(recipient.avatarPath, "?v=").concat(recipient.status) : null;
44
- return /*#__PURE__*/React.createElement(Avatar, rest, image ? /*#__PURE__*/React.createElement("img", {
52
+ var image = recipient.avatarPath && recipient.status ? "".concat(client.options.uri).concat(recipient.avatarPath).concat(recipient.avatarPath.includes('?') ? '&' : '?', "v=").concat(recipient.status) : null; // The avatar can become unreachable (eg. the related sharing is revoked
53
+ // while its members are still rendered), in which case the image request
54
+ // fails. Without a fallback the browser shows a broken-image icon, so we
55
+ // render the initials instead.
56
+
57
+ return /*#__PURE__*/React.createElement(Avatar, rest, image && erroredSrc !== image ? /*#__PURE__*/React.createElement("img", {
45
58
  width: "100%",
46
59
  height: "100%",
47
- src: image
60
+ src: image,
61
+ onError: function onError() {
62
+ return setErroredSrc(image);
63
+ }
48
64
  }) : getInitials(recipient));
49
65
  };
50
66
 
@@ -0,0 +1,77 @@
1
+ import { render, fireEvent } from '@testing-library/react';
2
+ import React from 'react';
3
+ import { createMockClient } from 'cozy-client';
4
+ import { MemberAvatar } from './MemberAvatar';
5
+ import AppLike from '../../../test/AppLike';
6
+ describe('MemberAvatar', function () {
7
+ var client = createMockClient({});
8
+ client.options = {
9
+ uri: 'http://cozy.example.com'
10
+ };
11
+
12
+ var setup = function setup(recipient) {
13
+ return render( /*#__PURE__*/React.createElement(AppLike, {
14
+ client: client
15
+ }, /*#__PURE__*/React.createElement(MemberAvatar, {
16
+ recipient: recipient
17
+ })));
18
+ };
19
+
20
+ it('renders nothing when the recipient is missing', function () {
21
+ var _setup = setup(undefined),
22
+ container = _setup.container; // The component returns null; only the provider wrappers remain, with no
23
+ // avatar rendered inside.
24
+
25
+
26
+ expect(container.querySelector('[class*="MuiAvatar-root"]')).toBeNull();
27
+ expect(container.querySelector('img')).toBeNull();
28
+ });
29
+ it('renders the avatar image when the recipient has an avatarPath and a status', function () {
30
+ var _setup2 = setup({
31
+ public_name: 'Bob',
32
+ status: 'owner',
33
+ avatarPath: '/sharings/123/recipients/0/avatar'
34
+ }),
35
+ container = _setup2.container;
36
+
37
+ var img = container.querySelector('img');
38
+ expect(img).toBeTruthy();
39
+ expect(img.getAttribute('src')).toBe('http://cozy.example.com/sharings/123/recipients/0/avatar?v=owner');
40
+ });
41
+ it('appends the cache-busting param with & when avatarPath already has a query string', function () {
42
+ var _setup3 = setup({
43
+ public_name: 'Bob',
44
+ status: 'owner',
45
+ avatarPath: '/public/avatar?fallback=initials'
46
+ }),
47
+ container = _setup3.container;
48
+
49
+ expect(container.querySelector('img').getAttribute('src')).toBe('http://cozy.example.com/public/avatar?fallback=initials&v=owner');
50
+ });
51
+ it('renders the initials when there is no avatarPath', function () {
52
+ var _setup4 = setup({
53
+ public_name: 'Bob',
54
+ status: 'owner'
55
+ }),
56
+ container = _setup4.container,
57
+ getByText = _setup4.getByText;
58
+
59
+ expect(container.querySelector('img')).toBeNull();
60
+ expect(getByText('B')).toBeTruthy();
61
+ });
62
+ it('falls back to the initials when the avatar image fails to load', function () {
63
+ var _setup5 = setup({
64
+ public_name: 'Bob',
65
+ status: 'owner',
66
+ avatarPath: '/sharings/123/recipients/0/avatar'
67
+ }),
68
+ container = _setup5.container,
69
+ getByText = _setup5.getByText;
70
+
71
+ var img = container.querySelector('img');
72
+ expect(img).toBeTruthy();
73
+ fireEvent.error(img);
74
+ expect(container.querySelector('img')).toBeNull();
75
+ expect(getByText('B')).toBeTruthy();
76
+ });
77
+ });
@@ -1,7 +1,13 @@
1
1
  import React from 'react';
2
2
  import { useClient, useQuery, hasQueryBeenLoaded } from 'cozy-client';
3
3
  import MemberRecipient from './MemberRecipient';
4
- import { buildInstanceSettingsQuery } from '../../queries/queries';
4
+ import { buildInstanceSettingsQuery } from '../../queries/queries'; // When the owner is not part of a sharing's members (eg. the folder only has
5
+ // a link), there is no per-sharing avatar to reference. The instance's public
6
+ // avatar still holds the owner's picture. fallback=initials makes the stack
7
+ // serve a generated initials avatar (instead of the app icon) when no picture
8
+ // has been uploaded, matching how avatars are rendered everywhere else.
9
+
10
+ var OWNER_PUBLIC_AVATAR_PATH = '/public/avatar?fallback=initials';
5
11
 
6
12
  var OwnerRecipientDefault = function OwnerRecipientDefault() {
7
13
  var _instanceSettingsResu;
@@ -13,6 +19,7 @@ var OwnerRecipientDefault = function OwnerRecipientDefault() {
13
19
  isOwner: true,
14
20
  status: "owner",
15
21
  instance: client.options.uri,
22
+ avatarPath: OWNER_PUBLIC_AVATAR_PATH,
16
23
  public_name: instanceSettingsResult === null || instanceSettingsResult === void 0 || (_instanceSettingsResu = instanceSettingsResult.data) === null || _instanceSettingsResu === void 0 || (_instanceSettingsResu = _instanceSettingsResu.attributes) === null || _instanceSettingsResu === void 0 ? void 0 : _instanceSettingsResu.public_name
17
24
  });
18
25
  };
@@ -2,7 +2,10 @@ import React from 'react';
2
2
  import { useClient, useQuery, hasQueryBeenLoaded } from 'cozy-client';
3
3
  import MemberRecipientLite from './MemberRecipientLite';
4
4
  import withLocales from '../../hoc/withLocales';
5
- import { buildInstanceSettingsQuery } from '../../queries/queries';
5
+ import { buildInstanceSettingsQuery } from '../../queries/queries'; // See OwnerRecipientDefault: show the instance public avatar (with an initials
6
+ // fallback) when the owner has no per-sharing avatar to reference.
7
+
8
+ var OWNER_PUBLIC_AVATAR_PATH = '/public/avatar?fallback=initials';
6
9
 
7
10
  var OwnerRecipientDefaultLite = function OwnerRecipientDefaultLite() {
8
11
  var _instanceSettingsResu;
@@ -14,6 +17,7 @@ var OwnerRecipientDefaultLite = function OwnerRecipientDefaultLite() {
14
17
  recipient: {
15
18
  status: 'owner',
16
19
  instance: client.options.uri,
20
+ avatarPath: OWNER_PUBLIC_AVATAR_PATH,
17
21
  public_name: instanceSettingsResult === null || instanceSettingsResult === void 0 || (_instanceSettingsResu = instanceSettingsResult.data) === null || _instanceSettingsResu === void 0 || (_instanceSettingsResu = _instanceSettingsResu.attributes) === null || _instanceSettingsResu === void 0 ? void 0 : _instanceSettingsResu.public_name
18
22
  },
19
23
  isOwner: true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-sharing",
3
- "version": "33.4.0",
3
+ "version": "33.4.1",
4
4
  "description": "Provides sharing login for React applications.",
5
5
  "main": "dist/index.js",
6
6
  "author": "Cozy",
@@ -83,5 +83,5 @@
83
83
  "sideEffects": [
84
84
  "*.css"
85
85
  ],
86
- "gitHead": "2359cb47aa065de03f5491ba05114a860a44d9ca"
86
+ "gitHead": "a2e29d6c728ebf0fdc890666a9ec3505e90551ed"
87
87
  }
@@ -1,4 +1,4 @@
1
- import React from 'react'
1
+ import React, { useState } from 'react'
2
2
 
3
3
  import { useClient } from 'cozy-client'
4
4
  import Avatar from 'cozy-ui/transpiled/react/Avatar'
@@ -8,6 +8,10 @@ import { getInitials } from '../../models'
8
8
 
9
9
  const MemberAvatar = ({ recipient, ...rest }) => {
10
10
  const client = useClient()
11
+ // Remember which image url failed to load so we can fall back to the
12
+ // initials. Storing the url (instead of a boolean) means a new url is
13
+ // retried automatically, without an effect to reset the error state.
14
+ const [erroredSrc, setErroredSrc] = useState(null)
11
15
 
12
16
  // There are cases when due to apparent memory leaks, the recipient does not exist
13
17
  // This will trigger an unhanded error in the Avatar component and crash the app
@@ -31,13 +35,24 @@ const MemberAvatar = ({ recipient, ...rest }) => {
31
35
  */
32
36
  const image =
33
37
  recipient.avatarPath && recipient.status
34
- ? `${client.options.uri}${recipient.avatarPath}?v=${recipient.status}`
38
+ ? `${client.options.uri}${recipient.avatarPath}${
39
+ recipient.avatarPath.includes('?') ? '&' : '?'
40
+ }v=${recipient.status}`
35
41
  : null
36
42
 
43
+ // The avatar can become unreachable (eg. the related sharing is revoked
44
+ // while its members are still rendered), in which case the image request
45
+ // fails. Without a fallback the browser shows a broken-image icon, so we
46
+ // render the initials instead.
37
47
  return (
38
48
  <Avatar {...rest}>
39
- {image ? (
40
- <img width="100%" height="100%" src={image} />
49
+ {image && erroredSrc !== image ? (
50
+ <img
51
+ width="100%"
52
+ height="100%"
53
+ src={image}
54
+ onError={() => setErroredSrc(image)}
55
+ />
41
56
  ) : (
42
57
  getInitials(recipient)
43
58
  )}
@@ -0,0 +1,80 @@
1
+ import { render, fireEvent } from '@testing-library/react'
2
+ import React from 'react'
3
+
4
+ import { createMockClient } from 'cozy-client'
5
+
6
+ import { MemberAvatar } from './MemberAvatar'
7
+ import AppLike from '../../../test/AppLike'
8
+
9
+ describe('MemberAvatar', () => {
10
+ const client = createMockClient({})
11
+ client.options = { uri: 'http://cozy.example.com' }
12
+
13
+ const setup = recipient =>
14
+ render(
15
+ <AppLike client={client}>
16
+ <MemberAvatar recipient={recipient} />
17
+ </AppLike>
18
+ )
19
+
20
+ it('renders nothing when the recipient is missing', () => {
21
+ const { container } = setup(undefined)
22
+
23
+ // The component returns null; only the provider wrappers remain, with no
24
+ // avatar rendered inside.
25
+ expect(container.querySelector('[class*="MuiAvatar-root"]')).toBeNull()
26
+ expect(container.querySelector('img')).toBeNull()
27
+ })
28
+
29
+ it('renders the avatar image when the recipient has an avatarPath and a status', () => {
30
+ const { container } = setup({
31
+ public_name: 'Bob',
32
+ status: 'owner',
33
+ avatarPath: '/sharings/123/recipients/0/avatar'
34
+ })
35
+
36
+ const img = container.querySelector('img')
37
+ expect(img).toBeTruthy()
38
+ expect(img.getAttribute('src')).toBe(
39
+ 'http://cozy.example.com/sharings/123/recipients/0/avatar?v=owner'
40
+ )
41
+ })
42
+
43
+ it('appends the cache-busting param with & when avatarPath already has a query string', () => {
44
+ const { container } = setup({
45
+ public_name: 'Bob',
46
+ status: 'owner',
47
+ avatarPath: '/public/avatar?fallback=initials'
48
+ })
49
+
50
+ expect(container.querySelector('img').getAttribute('src')).toBe(
51
+ 'http://cozy.example.com/public/avatar?fallback=initials&v=owner'
52
+ )
53
+ })
54
+
55
+ it('renders the initials when there is no avatarPath', () => {
56
+ const { container, getByText } = setup({
57
+ public_name: 'Bob',
58
+ status: 'owner'
59
+ })
60
+
61
+ expect(container.querySelector('img')).toBeNull()
62
+ expect(getByText('B')).toBeTruthy()
63
+ })
64
+
65
+ it('falls back to the initials when the avatar image fails to load', () => {
66
+ const { container, getByText } = setup({
67
+ public_name: 'Bob',
68
+ status: 'owner',
69
+ avatarPath: '/sharings/123/recipients/0/avatar'
70
+ })
71
+
72
+ const img = container.querySelector('img')
73
+ expect(img).toBeTruthy()
74
+
75
+ fireEvent.error(img)
76
+
77
+ expect(container.querySelector('img')).toBeNull()
78
+ expect(getByText('B')).toBeTruthy()
79
+ })
80
+ })
@@ -5,6 +5,13 @@ import { useClient, useQuery, hasQueryBeenLoaded } from 'cozy-client'
5
5
  import MemberRecipient from './MemberRecipient'
6
6
  import { buildInstanceSettingsQuery } from '../../queries/queries'
7
7
 
8
+ // When the owner is not part of a sharing's members (eg. the folder only has
9
+ // a link), there is no per-sharing avatar to reference. The instance's public
10
+ // avatar still holds the owner's picture. fallback=initials makes the stack
11
+ // serve a generated initials avatar (instead of the app icon) when no picture
12
+ // has been uploaded, matching how avatars are rendered everywhere else.
13
+ const OWNER_PUBLIC_AVATAR_PATH = '/public/avatar?fallback=initials'
14
+
8
15
  const OwnerRecipientDefault = () => {
9
16
  const client = useClient()
10
17
 
@@ -20,6 +27,7 @@ const OwnerRecipientDefault = () => {
20
27
  isOwner={true}
21
28
  status="owner"
22
29
  instance={client.options.uri}
30
+ avatarPath={OWNER_PUBLIC_AVATAR_PATH}
23
31
  public_name={instanceSettingsResult?.data?.attributes?.public_name}
24
32
  />
25
33
  )
@@ -6,6 +6,10 @@ import MemberRecipientLite from './MemberRecipientLite'
6
6
  import withLocales from '../../hoc/withLocales'
7
7
  import { buildInstanceSettingsQuery } from '../../queries/queries'
8
8
 
9
+ // See OwnerRecipientDefault: show the instance public avatar (with an initials
10
+ // fallback) when the owner has no per-sharing avatar to reference.
11
+ const OWNER_PUBLIC_AVATAR_PATH = '/public/avatar?fallback=initials'
12
+
9
13
  const OwnerRecipientDefaultLite = () => {
10
14
  const client = useClient()
11
15
 
@@ -21,6 +25,7 @@ const OwnerRecipientDefaultLite = () => {
21
25
  recipient={{
22
26
  status: 'owner',
23
27
  instance: client.options.uri,
28
+ avatarPath: OWNER_PUBLIC_AVATAR_PATH,
24
29
  public_name: instanceSettingsResult?.data?.attributes?.public_name
25
30
  }}
26
31
  isOwner={true}