@atlaskit/media-viewer 54.3.8 → 54.5.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,5 +1,33 @@
1
1
  # @atlaskit/media-viewer
2
2
 
3
+ ## 54.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`2f56c78f969b8`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/2f56c78f969b8) -
8
+ Update i18n NPM package versions for teamwork-graph (Group 16)
9
+
10
+ ### Patch Changes
11
+
12
+ - Updated dependencies
13
+
14
+ ## 54.4.0
15
+
16
+ ### Minor Changes
17
+
18
+ - [`ecf905d329e42`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/ecf905d329e42) -
19
+ Add diagnostic context to the imageviewer-src-onerror failure (errorDetail now report mimeType,
20
+ decode dimensions and failed source type instead of unknown)
21
+ - [`a654b7573d0a2`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/a654b7573d0a2) -
22
+ Route image MIME types that the browser cannot natively decode (e.g. HEIC/HEIF, PSD, TIFF) to the
23
+ unsupported/download preview view instead of a guaranteed-failing `<img>`. These are reported via
24
+ the `previewUnsupported` analytics event rather than `loadFailed`. Behind the
25
+ `platform_media_unsupported_mime_routing` feature gate.
26
+
27
+ ### Patch Changes
28
+
29
+ - Updated dependencies
30
+
3
31
  ## 54.3.8
4
32
 
5
33
  ### Patch Changes
@@ -10,7 +10,7 @@ exports.packageVersion = exports.packageName = void 0;
10
10
  var _analytics = require("@atlaskit/media-common/analytics");
11
11
  var componentName = exports.component = exports.componentName = 'mediaViewer';
12
12
  var packageName = exports.packageName = "@atlaskit/media-viewer";
13
- var packageVersion = exports.packageVersion = "54.3.7";
13
+ var packageVersion = exports.packageVersion = "54.4.0";
14
14
  function getFileAttributes(fileState) {
15
15
  if (!fileState) {
16
16
  return {
@@ -13,7 +13,7 @@ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
13
13
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
14
14
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
15
15
  var packageName = "@atlaskit/media-viewer";
16
- var packageVersion = "54.3.7";
16
+ var packageVersion = "54.4.0";
17
17
  var ufoExperience;
18
18
  var getExperience = function getExperience() {
19
19
  if (!ufoExperience) {
@@ -5,12 +5,12 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.getErrorMessageFromError = exports.errorReasonToMessages = exports.default = exports.ErrorMessage = void 0;
8
- var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
8
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
10
9
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
11
10
  var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
12
11
  var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
13
12
  var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
13
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
14
14
  var _react = _interopRequireDefault(require("react"));
15
15
  var _reactIntl = require("react-intl");
16
16
  var _mediaUi = require("@atlaskit/media-ui");
@@ -51,11 +51,12 @@ var ErrorMessage = exports.ErrorMessage = /*#__PURE__*/function (_React$Componen
51
51
  var _this$props = this.props,
52
52
  formatMessage = _this$props.intl.formatMessage,
53
53
  error = _this$props.error;
54
- // Non-ZIP archives (e.g. RAR, TAR, 7z) aren't a load failure - the
54
+ // Non-ZIP archives (e.g. RAR, TAR, 7z) and image MIME types that no browser
55
+ // can natively decode (e.g. HEIC/HEIF, PSD, TIFF) aren't load failures - the
55
56
  // browser-based viewer simply can't preview them. Show a dedicated
56
57
  // "Unsupported file format" heading instead of the generic
57
58
  // "Something went wrong" copy.
58
- if ((0, _errors.getPrimaryErrorReason)(error) === 'archiveviewer-not-zip') {
59
+ if ((0, _errors.getPrimaryErrorReason)(error) === 'archiveviewer-not-zip' || (0, _errors.getPrimaryErrorReason)(error) === 'imageviewer-unsupported-mime') {
59
60
  return {
60
61
  icon: errorLoadingFileImage(formatMessage),
61
62
  messages: [_mediaUi.messages.unsupported_file_format, _mediaUi.messages.archive_format_not_supported]
@@ -95,6 +96,10 @@ var ErrorMessage = exports.ErrorMessage = /*#__PURE__*/function (_React$Componen
95
96
  (0, _ufoExperiences.failMediaFileUfoExperience)(failMediaFileUfoExperiencePayload);
96
97
  }
97
98
  }
99
+
100
+ // Error reasons that represent a format the browser-based viewer simply can't
101
+ // preview (not a load failure). These are reported as the informational,
102
+ // non-SLI `previewUnsupported` metric instead of `loadFailed`.
98
103
  }, {
99
104
  key: "render",
100
105
  value: function render() {
@@ -112,10 +117,16 @@ var ErrorMessage = exports.ErrorMessage = /*#__PURE__*/function (_React$Componen
112
117
  })), this.props.children);
113
118
  }
114
119
  }], [{
120
+ key: "isPreviewUnsupported",
121
+ value: function isPreviewUnsupported(error) {
122
+ return ErrorMessage.previewUnsupportedReasons.includes((0, _errors.getPrimaryErrorReason)(error));
123
+ }
124
+ }, {
115
125
  key: "getEventPayload",
116
126
  value: function getEventPayload(error, fileId, fileState, traceContext) {
117
- if (fileState && (0, _errors.getPrimaryErrorReason)(error) === 'unsupported') {
118
- // this is not an SLI, its just a useful metric for unsupported
127
+ if (fileState && ErrorMessage.isPreviewUnsupported(error)) {
128
+ // this is not an SLI (load failure), its just a useful metric for files
129
+ // whose format the browser-based viewer can't preview.
119
130
  return (0, _previewUnsupported.createPreviewUnsupportedEvent)(fileState);
120
131
  } else {
121
132
  return (0, _loadFailed.createLoadFailedEvent)(fileId, error, fileState, traceContext);
@@ -123,5 +134,6 @@ var ErrorMessage = exports.ErrorMessage = /*#__PURE__*/function (_React$Componen
123
134
  }
124
135
  }]);
125
136
  }(_react.default.Component); // @ts-ignore: [PIT-1685] Fails in post-office due to backwards incompatibility issue with React 18
137
+ (0, _defineProperty2.default)(ErrorMessage, "previewUnsupportedReasons", ['unsupported', 'imageviewer-unsupported-mime']);
126
138
  var ErroMsg = (0, _analyticsNext.withAnalyticsEvents)()((0, _reactIntl.injectIntl)(ErrorMessage));
127
139
  var _default = exports.default = ErroMsg;
@@ -5,12 +5,15 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.MediaViewerError = exports.ArchiveViewerError = void 0;
8
+ exports.buildImgErrorDiagnostics = buildImgErrorDiagnostics;
9
+ exports.classifyFailedSrc = classifyFailedSrc;
8
10
  exports.getErrorDetail = getErrorDetail;
9
11
  exports.getPrimaryErrorReason = getPrimaryErrorReason;
10
12
  exports.getRequestMetadata = getRequestMetadata;
11
13
  exports.getSecondaryErrorReason = getSecondaryErrorReason;
12
14
  exports.isArchiveViewerError = isArchiveViewerError;
13
15
  exports.isMediaViewerError = isMediaViewerError;
16
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
14
17
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
15
18
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
16
19
  var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
@@ -18,6 +21,7 @@ var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/ge
18
21
  var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
19
22
  var _wrapNativeSuper2 = _interopRequireDefault(require("@babel/runtime/helpers/wrapNativeSuper"));
20
23
  var _mediaClient = require("@atlaskit/media-client");
24
+ var _isMimeTypeSupportedByBrowser = require("@atlaskit/media-common/isMimeTypeSupportedByBrowser");
21
25
  function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
22
26
  function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
23
27
  var MediaViewerError = exports.MediaViewerError = /*#__PURE__*/function (_Error) {
@@ -91,4 +95,64 @@ function getRequestMetadata(error) {
91
95
  if ((0, _mediaClient.isCommonMediaClientError)(secondaryError)) {
92
96
  return secondaryError.metadata;
93
97
  }
98
+ }
99
+
100
+ /**
101
+ * Classifies the failed `<img>` source into a coarse, non-sensitive enum so we can
102
+ * tell which kind of source failed without leaking the (potentially signed) url.
103
+ */
104
+ function classifyFailedSrc(currentSrc) {
105
+ if (!currentSrc) {
106
+ return 'unknown';
107
+ }
108
+ if (currentSrc.startsWith('blob:')) {
109
+ return 'blob';
110
+ }
111
+ if (currentSrc.startsWith('data:')) {
112
+ return 'data';
113
+ }
114
+ if (currentSrc.startsWith('http:') || currentSrc.startsWith('https:')) {
115
+ return 'remote';
116
+ }
117
+ return 'unknown';
118
+ }
119
+
120
+ /**
121
+ * Builds a descriptive `secondaryError` for an `<img>` onerror so the downstream
122
+ * `loadFailed` analytics event reports a meaningful `error`/`errorDetail` instead of
123
+ * the opaque `unknown`/`unknown`. The returned Error's `message` captures decode
124
+ * diagnostics as a comma-separated `key=value` string (undefined fields are omitted):
125
+ * - whether the browser decoded any pixels (`naturalWidth`/`naturalHeight === 0`)
126
+ * - the file MIME type and whether it is natively browser-decodable
127
+ * - which source failed (preview blob vs. original-binary/HD url)
128
+ *
129
+ * @example
130
+ * // message:
131
+ * "mimeType=image/png, isBrowserDecodable=true, naturalWidth=0, naturalHeight=0, failedSrcType=blob"
132
+ */
133
+ function buildImgErrorDiagnostics(item, event) {
134
+ var img = event === null || event === void 0 ? void 0 : event.currentTarget;
135
+ var mimeType = item && !(0, _mediaClient.isErrorFileState)(item) ? item.mimeType : undefined;
136
+ var isBrowserDecodable = !!mimeType && (0, _isMimeTypeSupportedByBrowser.isImageMimeTypeSupportedByBrowser)(mimeType);
137
+ var naturalWidth = img === null || img === void 0 ? void 0 : img.naturalWidth;
138
+ var naturalHeight = img === null || img === void 0 ? void 0 : img.naturalHeight;
139
+ var failedSrcType = classifyFailedSrc(img === null || img === void 0 ? void 0 : img.currentSrc);
140
+ var diagnostics = {
141
+ mimeType: mimeType,
142
+ isBrowserDecodable: isBrowserDecodable,
143
+ naturalWidth: naturalWidth,
144
+ naturalHeight: naturalHeight,
145
+ failedSrcType: failedSrcType
146
+ };
147
+ var detail = Object.entries(diagnostics).filter(function (_ref3) {
148
+ var _ref4 = (0, _slicedToArray2.default)(_ref3, 2),
149
+ value = _ref4[1];
150
+ return value !== undefined;
151
+ }).map(function (_ref5) {
152
+ var _ref6 = (0, _slicedToArray2.default)(_ref5, 2),
153
+ key = _ref6[0],
154
+ value = _ref6[1];
155
+ return "".concat(key, "=").concat(value);
156
+ }).join(', ');
157
+ return new Error(detail);
94
158
  }
@@ -17,6 +17,7 @@ var _react = _interopRequireDefault(require("react"));
17
17
  var _mediaClient = require("@atlaskit/media-client");
18
18
  var _mediaCommon = require("@atlaskit/media-common");
19
19
  var _mediaUi = require("@atlaskit/media-ui");
20
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
20
21
  var _domain = require("../../domain");
21
22
  var _errors = require("../../errors");
22
23
  var _interactiveImg = require("./interactive-img");
@@ -43,8 +44,14 @@ var ImageViewer = exports.ImageViewer = /*#__PURE__*/function (_BaseViewer) {
43
44
  _this.props.onLoad();
44
45
  _this.onMediaDisplayed();
45
46
  });
46
- (0, _defineProperty2.default)(_this, "onImgError", function () {
47
- _this.props.onError(new _errors.MediaViewerError('imageviewer-src-onerror'));
47
+ (0, _defineProperty2.default)(_this, "onImgError", function (event) {
48
+ var secondaryError;
49
+ try {
50
+ secondaryError = (0, _errors.buildImgErrorDiagnostics)(_this.props.item, event);
51
+ } catch (_unused) {
52
+ secondaryError = undefined;
53
+ }
54
+ _this.props.onError(new _errors.MediaViewerError('imageviewer-src-onerror', secondaryError));
48
55
  });
49
56
  return _this;
50
57
  }
@@ -60,7 +67,7 @@ var ImageViewer = exports.ImageViewer = /*#__PURE__*/function (_BaseViewer) {
60
67
  key: "init",
61
68
  value: function () {
62
69
  var _init = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
63
- var _this$props, fileState, mediaClient, collectionName, traceContext, controller, orientation, objectUrl, originalBinaryImageUrl, isLocalFileReference, clientId, preview, _yield$preview, value, origin, item, response, imgError, _t, _t2, _t3;
70
+ var _this$props, fileState, mediaClient, collectionName, traceContext, controller, orientation, objectUrl, originalBinaryImageUrl, isLocalFileReference, clientId, preview, _yield$preview, value, origin, item, response, unsupportedError, imgError, _t, _t2, _t3;
64
71
  return _regenerator.default.wrap(function (_context) {
65
72
  while (1) switch (_context.prev = _context.next) {
66
73
  case 0:
@@ -142,7 +149,11 @@ var ImageViewer = exports.ImageViewer = /*#__PURE__*/function (_BaseViewer) {
142
149
  case 14:
143
150
  if (!(!isLocalFileReference &&
144
151
  // objectUrl at this point is binary file already
145
- !(0, _mediaClient.isErrorFileState)(fileState) && fileState.status !== 'uploading' && fileState.mediaType === 'image' && (0, _mediaCommon.isImageMimeTypeSupportedByBrowser)(fileState.mimeType))) {
152
+ !(0, _mediaClient.isErrorFileState)(fileState) && fileState.status !== 'uploading' && fileState.mediaType === 'image')) {
153
+ _context.next = 17;
154
+ break;
155
+ }
156
+ if (!(0, _mediaCommon.isImageMimeTypeSupportedByBrowser)(fileState.mimeType)) {
146
157
  _context.next = 16;
147
158
  break;
148
159
  }
@@ -150,7 +161,25 @@ var ImageViewer = exports.ImageViewer = /*#__PURE__*/function (_BaseViewer) {
150
161
  return mediaClient.file.getFileBinaryURL(fileState.id, collectionName);
151
162
  case 15:
152
163
  originalBinaryImageUrl = _context.sent;
164
+ _context.next = 17;
165
+ break;
153
166
  case 16:
167
+ if (!(0, _platformFeatureFlags.fg)('platform_media_unsupported_mime_routing')) {
168
+ _context.next = 17;
169
+ break;
170
+ }
171
+ // Route unsupported MIME types browser can't natively decode straight to the
172
+ // unsupported/download view instead of handing a guaranteed-undecodable blob
173
+ // to <img>, which would only ever fire onerror and
174
+ // surface as an opaque `imageviewer-src-onerror` failure.
175
+ this.revokeObjectUrl(objectUrl);
176
+ unsupportedError = new _errors.MediaViewerError('imageviewer-unsupported-mime');
177
+ this.setState({
178
+ content: _domain.Outcome.failed(unsupportedError)
179
+ });
180
+ this.props.onError(unsupportedError);
181
+ return _context.abrupt("return");
182
+ case 17:
154
183
  this.setState({
155
184
  content: _domain.Outcome.successful({
156
185
  objectUrl: objectUrl,
@@ -159,10 +188,10 @@ var ImageViewer = exports.ImageViewer = /*#__PURE__*/function (_BaseViewer) {
159
188
  clientId: clientId
160
189
  })
161
190
  });
162
- _context.next = 18;
191
+ _context.next = 19;
163
192
  break;
164
- case 17:
165
- _context.prev = 17;
193
+ case 18:
194
+ _context.prev = 18;
166
195
  _t3 = _context["catch"](2);
167
196
  // TODO : properly handle aborted requests (MS-2843)
168
197
  if (!(controller !== null && controller !== void 0 && controller.signal.aborted)) {
@@ -172,11 +201,11 @@ var ImageViewer = exports.ImageViewer = /*#__PURE__*/function (_BaseViewer) {
172
201
  });
173
202
  this.props.onError(imgError);
174
203
  }
175
- case 18:
204
+ case 19:
176
205
  case "end":
177
206
  return _context.stop();
178
207
  }
179
- }, _callee, this, [[2, 17], [3, 5]]);
208
+ }, _callee, this, [[2, 18], [3, 5]]);
180
209
  }));
181
210
  function init() {
182
211
  return _init.apply(this, arguments);
@@ -1,7 +1,7 @@
1
1
  import { ANALYTICS_MEDIA_CHANNEL, sanitiseAnalyticsPayload } from '@atlaskit/media-common/analytics';
2
2
  const componentName = 'mediaViewer';
3
3
  const packageName = "@atlaskit/media-viewer";
4
- const packageVersion = "54.3.7";
4
+ const packageVersion = "54.4.0";
5
5
  export { packageName, packageVersion, componentName, componentName as component };
6
6
  export function getFileAttributes(fileState) {
7
7
  if (!fileState) {
@@ -3,7 +3,7 @@ import { getMediaEnvironment, getMediaRegion } from '@atlaskit/media-client';
3
3
  import { getFeatureFlagKeysAllProducts } from '@atlaskit/media-common';
4
4
  import { fg } from '@atlaskit/platform-feature-flags';
5
5
  const packageName = "@atlaskit/media-viewer";
6
- const packageVersion = "54.3.7";
6
+ const packageVersion = "54.4.0";
7
7
  let ufoExperience;
8
8
  const getExperience = () => {
9
9
  if (!ufoExperience) {
@@ -1,3 +1,4 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
1
2
  import React from 'react';
2
3
  import { FormattedMessage, injectIntl } from 'react-intl';
3
4
  import { messages as i18nMessages } from '@atlaskit/media-ui';
@@ -26,11 +27,12 @@ export class ErrorMessage extends React.Component {
26
27
  },
27
28
  error
28
29
  } = this.props;
29
- // Non-ZIP archives (e.g. RAR, TAR, 7z) aren't a load failure - the
30
+ // Non-ZIP archives (e.g. RAR, TAR, 7z) and image MIME types that no browser
31
+ // can natively decode (e.g. HEIC/HEIF, PSD, TIFF) aren't load failures - the
30
32
  // browser-based viewer simply can't preview them. Show a dedicated
31
33
  // "Unsupported file format" heading instead of the generic
32
34
  // "Something went wrong" copy.
33
- if (getPrimaryErrorReason(error) === 'archiveviewer-not-zip') {
35
+ if (getPrimaryErrorReason(error) === 'archiveviewer-not-zip' || getPrimaryErrorReason(error) === 'imageviewer-unsupported-mime') {
34
36
  return {
35
37
  icon: errorLoadingFileImage(formatMessage),
36
38
  messages: [i18nMessages.unsupported_file_format, i18nMessages.archive_format_not_supported]
@@ -73,9 +75,18 @@ export class ErrorMessage extends React.Component {
73
75
  failMediaFileUfoExperience(failMediaFileUfoExperiencePayload);
74
76
  }
75
77
  }
78
+
79
+ // Error reasons that represent a format the browser-based viewer simply can't
80
+ // preview (not a load failure). These are reported as the informational,
81
+ // non-SLI `previewUnsupported` metric instead of `loadFailed`.
82
+
83
+ static isPreviewUnsupported(error) {
84
+ return ErrorMessage.previewUnsupportedReasons.includes(getPrimaryErrorReason(error));
85
+ }
76
86
  static getEventPayload(error, fileId, fileState, traceContext) {
77
- if (fileState && getPrimaryErrorReason(error) === 'unsupported') {
78
- // this is not an SLI, its just a useful metric for unsupported
87
+ if (fileState && ErrorMessage.isPreviewUnsupported(error)) {
88
+ // this is not an SLI (load failure), its just a useful metric for files
89
+ // whose format the browser-based viewer can't preview.
79
90
  return createPreviewUnsupportedEvent(fileState);
80
91
  } else {
81
92
  return createLoadFailedEvent(fileId, error, fileState, traceContext);
@@ -95,5 +106,6 @@ export class ErrorMessage extends React.Component {
95
106
  }
96
107
 
97
108
  // @ts-ignore: [PIT-1685] Fails in post-office due to backwards incompatibility issue with React 18
109
+ _defineProperty(ErrorMessage, "previewUnsupportedReasons", ['unsupported', 'imageviewer-unsupported-mime']);
98
110
  const ErroMsg = withAnalyticsEvents()(injectIntl(ErrorMessage));
99
111
  export default ErroMsg;
@@ -1,4 +1,5 @@
1
- import { isCommonMediaClientError } from '@atlaskit/media-client';
1
+ import { isCommonMediaClientError, isErrorFileState } from '@atlaskit/media-client';
2
+ import { isImageMimeTypeSupportedByBrowser } from '@atlaskit/media-common/isMimeTypeSupportedByBrowser';
2
3
  export class MediaViewerError extends Error {
3
4
  constructor(primaryReason, secondaryError) {
4
5
  super(primaryReason);
@@ -64,4 +65,55 @@ export function getRequestMetadata(error) {
64
65
  if (isCommonMediaClientError(secondaryError)) {
65
66
  return secondaryError.metadata;
66
67
  }
68
+ }
69
+
70
+ /**
71
+ * Classifies the failed `<img>` source into a coarse, non-sensitive enum so we can
72
+ * tell which kind of source failed without leaking the (potentially signed) url.
73
+ */
74
+ export function classifyFailedSrc(currentSrc) {
75
+ if (!currentSrc) {
76
+ return 'unknown';
77
+ }
78
+ if (currentSrc.startsWith('blob:')) {
79
+ return 'blob';
80
+ }
81
+ if (currentSrc.startsWith('data:')) {
82
+ return 'data';
83
+ }
84
+ if (currentSrc.startsWith('http:') || currentSrc.startsWith('https:')) {
85
+ return 'remote';
86
+ }
87
+ return 'unknown';
88
+ }
89
+
90
+ /**
91
+ * Builds a descriptive `secondaryError` for an `<img>` onerror so the downstream
92
+ * `loadFailed` analytics event reports a meaningful `error`/`errorDetail` instead of
93
+ * the opaque `unknown`/`unknown`. The returned Error's `message` captures decode
94
+ * diagnostics as a comma-separated `key=value` string (undefined fields are omitted):
95
+ * - whether the browser decoded any pixels (`naturalWidth`/`naturalHeight === 0`)
96
+ * - the file MIME type and whether it is natively browser-decodable
97
+ * - which source failed (preview blob vs. original-binary/HD url)
98
+ *
99
+ * @example
100
+ * // message:
101
+ * "mimeType=image/png, isBrowserDecodable=true, naturalWidth=0, naturalHeight=0, failedSrcType=blob"
102
+ */
103
+ export function buildImgErrorDiagnostics(item, event) {
104
+ const img = event === null || event === void 0 ? void 0 : event.currentTarget;
105
+ const mimeType = item && !isErrorFileState(item) ? item.mimeType : undefined;
106
+ const isBrowserDecodable = !!mimeType && isImageMimeTypeSupportedByBrowser(mimeType);
107
+ const naturalWidth = img === null || img === void 0 ? void 0 : img.naturalWidth;
108
+ const naturalHeight = img === null || img === void 0 ? void 0 : img.naturalHeight;
109
+ const failedSrcType = classifyFailedSrc(img === null || img === void 0 ? void 0 : img.currentSrc);
110
+ const diagnostics = {
111
+ mimeType,
112
+ isBrowserDecodable,
113
+ naturalWidth,
114
+ naturalHeight,
115
+ failedSrcType
116
+ };
117
+ const detail = Object.entries(diagnostics).filter(([, value]) => value !== undefined).map(([key, value]) => `${key}=${value}`).join(', ');
118
+ return new Error(detail);
67
119
  }
@@ -3,8 +3,9 @@ import React from 'react';
3
3
  import { isImageRepresentationReady, isErrorFileState, addFileAttrsToUrl } from '@atlaskit/media-client';
4
4
  import { isImageMimeTypeSupportedByBrowser } from '@atlaskit/media-common';
5
5
  import { getOrientation } from '@atlaskit/media-ui';
6
+ import { fg } from '@atlaskit/platform-feature-flags';
6
7
  import { Outcome } from '../../domain';
7
- import { MediaViewerError } from '../../errors';
8
+ import { buildImgErrorDiagnostics, MediaViewerError } from '../../errors';
8
9
  import { InteractiveImg } from './interactive-img';
9
10
  import { BaseViewer } from '../base-viewer';
10
11
  function processedFileStateToMediaItem(file) {
@@ -22,8 +23,14 @@ export class ImageViewer extends BaseViewer {
22
23
  this.props.onLoad();
23
24
  this.onMediaDisplayed();
24
25
  });
25
- _defineProperty(this, "onImgError", () => {
26
- this.props.onError(new MediaViewerError('imageviewer-src-onerror'));
26
+ _defineProperty(this, "onImgError", event => {
27
+ let secondaryError;
28
+ try {
29
+ secondaryError = buildImgErrorDiagnostics(this.props.item, event);
30
+ } catch {
31
+ secondaryError = undefined;
32
+ }
33
+ this.props.onError(new MediaViewerError('imageviewer-src-onerror', secondaryError));
27
34
  });
28
35
  }
29
36
  get initialState() {
@@ -86,8 +93,22 @@ export class ImageViewer extends BaseViewer {
86
93
  }
87
94
  if (!isLocalFileReference &&
88
95
  // objectUrl at this point is binary file already
89
- !isErrorFileState(fileState) && fileState.status !== 'uploading' && fileState.mediaType === 'image' && isImageMimeTypeSupportedByBrowser(fileState.mimeType)) {
90
- originalBinaryImageUrl = await mediaClient.file.getFileBinaryURL(fileState.id, collectionName);
96
+ !isErrorFileState(fileState) && fileState.status !== 'uploading' && fileState.mediaType === 'image') {
97
+ if (isImageMimeTypeSupportedByBrowser(fileState.mimeType)) {
98
+ originalBinaryImageUrl = await mediaClient.file.getFileBinaryURL(fileState.id, collectionName);
99
+ } else if (fg('platform_media_unsupported_mime_routing')) {
100
+ // Route unsupported MIME types browser can't natively decode straight to the
101
+ // unsupported/download view instead of handing a guaranteed-undecodable blob
102
+ // to <img>, which would only ever fire onerror and
103
+ // surface as an opaque `imageviewer-src-onerror` failure.
104
+ this.revokeObjectUrl(objectUrl);
105
+ const unsupportedError = new MediaViewerError('imageviewer-unsupported-mime');
106
+ this.setState({
107
+ content: Outcome.failed(unsupportedError)
108
+ });
109
+ this.props.onError(unsupportedError);
110
+ return;
111
+ }
91
112
  }
92
113
  this.setState({
93
114
  content: Outcome.successful({
@@ -1,7 +1,7 @@
1
1
  import { ANALYTICS_MEDIA_CHANNEL, sanitiseAnalyticsPayload } from '@atlaskit/media-common/analytics';
2
2
  var componentName = 'mediaViewer';
3
3
  var packageName = "@atlaskit/media-viewer";
4
- var packageVersion = "54.3.7";
4
+ var packageVersion = "54.4.0";
5
5
  export { packageName, packageVersion, componentName, componentName as component };
6
6
  export function getFileAttributes(fileState) {
7
7
  if (!fileState) {
@@ -6,7 +6,7 @@ import { getMediaEnvironment, getMediaRegion } from '@atlaskit/media-client';
6
6
  import { getFeatureFlagKeysAllProducts } from '@atlaskit/media-common';
7
7
  import { fg } from '@atlaskit/platform-feature-flags';
8
8
  var packageName = "@atlaskit/media-viewer";
9
- var packageVersion = "54.3.7";
9
+ var packageVersion = "54.4.0";
10
10
  var ufoExperience;
11
11
  var getExperience = function getExperience() {
12
12
  if (!ufoExperience) {
@@ -1,9 +1,9 @@
1
- import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
1
  import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
3
2
  import _createClass from "@babel/runtime/helpers/createClass";
4
3
  import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";
5
4
  import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
6
5
  import _inherits from "@babel/runtime/helpers/inherits";
6
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
7
7
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
8
8
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
9
9
  function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
@@ -44,11 +44,12 @@ export var ErrorMessage = /*#__PURE__*/function (_React$Component) {
44
44
  var _this$props = this.props,
45
45
  formatMessage = _this$props.intl.formatMessage,
46
46
  error = _this$props.error;
47
- // Non-ZIP archives (e.g. RAR, TAR, 7z) aren't a load failure - the
47
+ // Non-ZIP archives (e.g. RAR, TAR, 7z) and image MIME types that no browser
48
+ // can natively decode (e.g. HEIC/HEIF, PSD, TIFF) aren't load failures - the
48
49
  // browser-based viewer simply can't preview them. Show a dedicated
49
50
  // "Unsupported file format" heading instead of the generic
50
51
  // "Something went wrong" copy.
51
- if (getPrimaryErrorReason(error) === 'archiveviewer-not-zip') {
52
+ if (getPrimaryErrorReason(error) === 'archiveviewer-not-zip' || getPrimaryErrorReason(error) === 'imageviewer-unsupported-mime') {
52
53
  return {
53
54
  icon: errorLoadingFileImage(formatMessage),
54
55
  messages: [i18nMessages.unsupported_file_format, i18nMessages.archive_format_not_supported]
@@ -88,6 +89,10 @@ export var ErrorMessage = /*#__PURE__*/function (_React$Component) {
88
89
  failMediaFileUfoExperience(failMediaFileUfoExperiencePayload);
89
90
  }
90
91
  }
92
+
93
+ // Error reasons that represent a format the browser-based viewer simply can't
94
+ // preview (not a load failure). These are reported as the informational,
95
+ // non-SLI `previewUnsupported` metric instead of `loadFailed`.
91
96
  }, {
92
97
  key: "render",
93
98
  value: function render() {
@@ -105,10 +110,16 @@ export var ErrorMessage = /*#__PURE__*/function (_React$Component) {
105
110
  })), this.props.children);
106
111
  }
107
112
  }], [{
113
+ key: "isPreviewUnsupported",
114
+ value: function isPreviewUnsupported(error) {
115
+ return ErrorMessage.previewUnsupportedReasons.includes(getPrimaryErrorReason(error));
116
+ }
117
+ }, {
108
118
  key: "getEventPayload",
109
119
  value: function getEventPayload(error, fileId, fileState, traceContext) {
110
- if (fileState && getPrimaryErrorReason(error) === 'unsupported') {
111
- // this is not an SLI, its just a useful metric for unsupported
120
+ if (fileState && ErrorMessage.isPreviewUnsupported(error)) {
121
+ // this is not an SLI (load failure), its just a useful metric for files
122
+ // whose format the browser-based viewer can't preview.
112
123
  return createPreviewUnsupportedEvent(fileState);
113
124
  } else {
114
125
  return createLoadFailedEvent(fileId, error, fileState, traceContext);
@@ -118,5 +129,6 @@ export var ErrorMessage = /*#__PURE__*/function (_React$Component) {
118
129
  }(React.Component);
119
130
 
120
131
  // @ts-ignore: [PIT-1685] Fails in post-office due to backwards incompatibility issue with React 18
132
+ _defineProperty(ErrorMessage, "previewUnsupportedReasons", ['unsupported', 'imageviewer-unsupported-mime']);
121
133
  var ErroMsg = withAnalyticsEvents()(injectIntl(ErrorMessage));
122
134
  export default ErroMsg;
@@ -1,3 +1,4 @@
1
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
1
2
  import _createClass from "@babel/runtime/helpers/createClass";
2
3
  import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
3
4
  import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";
@@ -6,7 +7,8 @@ import _inherits from "@babel/runtime/helpers/inherits";
6
7
  import _wrapNativeSuper from "@babel/runtime/helpers/wrapNativeSuper";
7
8
  function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
8
9
  function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
9
- import { isCommonMediaClientError } from '@atlaskit/media-client';
10
+ import { isCommonMediaClientError, isErrorFileState } from '@atlaskit/media-client';
11
+ import { isImageMimeTypeSupportedByBrowser } from '@atlaskit/media-common/isMimeTypeSupportedByBrowser';
10
12
  export var MediaViewerError = /*#__PURE__*/function (_Error) {
11
13
  function MediaViewerError(primaryReason, secondaryError) {
12
14
  var _this;
@@ -78,4 +80,64 @@ export function getRequestMetadata(error) {
78
80
  if (isCommonMediaClientError(secondaryError)) {
79
81
  return secondaryError.metadata;
80
82
  }
83
+ }
84
+
85
+ /**
86
+ * Classifies the failed `<img>` source into a coarse, non-sensitive enum so we can
87
+ * tell which kind of source failed without leaking the (potentially signed) url.
88
+ */
89
+ export function classifyFailedSrc(currentSrc) {
90
+ if (!currentSrc) {
91
+ return 'unknown';
92
+ }
93
+ if (currentSrc.startsWith('blob:')) {
94
+ return 'blob';
95
+ }
96
+ if (currentSrc.startsWith('data:')) {
97
+ return 'data';
98
+ }
99
+ if (currentSrc.startsWith('http:') || currentSrc.startsWith('https:')) {
100
+ return 'remote';
101
+ }
102
+ return 'unknown';
103
+ }
104
+
105
+ /**
106
+ * Builds a descriptive `secondaryError` for an `<img>` onerror so the downstream
107
+ * `loadFailed` analytics event reports a meaningful `error`/`errorDetail` instead of
108
+ * the opaque `unknown`/`unknown`. The returned Error's `message` captures decode
109
+ * diagnostics as a comma-separated `key=value` string (undefined fields are omitted):
110
+ * - whether the browser decoded any pixels (`naturalWidth`/`naturalHeight === 0`)
111
+ * - the file MIME type and whether it is natively browser-decodable
112
+ * - which source failed (preview blob vs. original-binary/HD url)
113
+ *
114
+ * @example
115
+ * // message:
116
+ * "mimeType=image/png, isBrowserDecodable=true, naturalWidth=0, naturalHeight=0, failedSrcType=blob"
117
+ */
118
+ export function buildImgErrorDiagnostics(item, event) {
119
+ var img = event === null || event === void 0 ? void 0 : event.currentTarget;
120
+ var mimeType = item && !isErrorFileState(item) ? item.mimeType : undefined;
121
+ var isBrowserDecodable = !!mimeType && isImageMimeTypeSupportedByBrowser(mimeType);
122
+ var naturalWidth = img === null || img === void 0 ? void 0 : img.naturalWidth;
123
+ var naturalHeight = img === null || img === void 0 ? void 0 : img.naturalHeight;
124
+ var failedSrcType = classifyFailedSrc(img === null || img === void 0 ? void 0 : img.currentSrc);
125
+ var diagnostics = {
126
+ mimeType: mimeType,
127
+ isBrowserDecodable: isBrowserDecodable,
128
+ naturalWidth: naturalWidth,
129
+ naturalHeight: naturalHeight,
130
+ failedSrcType: failedSrcType
131
+ };
132
+ var detail = Object.entries(diagnostics).filter(function (_ref3) {
133
+ var _ref4 = _slicedToArray(_ref3, 2),
134
+ value = _ref4[1];
135
+ return value !== undefined;
136
+ }).map(function (_ref5) {
137
+ var _ref6 = _slicedToArray(_ref5, 2),
138
+ key = _ref6[0],
139
+ value = _ref6[1];
140
+ return "".concat(key, "=").concat(value);
141
+ }).join(', ');
142
+ return new Error(detail);
81
143
  }
@@ -12,8 +12,9 @@ import React from 'react';
12
12
  import { isImageRepresentationReady, isErrorFileState, addFileAttrsToUrl } from '@atlaskit/media-client';
13
13
  import { isImageMimeTypeSupportedByBrowser } from '@atlaskit/media-common';
14
14
  import { getOrientation } from '@atlaskit/media-ui';
15
+ import { fg } from '@atlaskit/platform-feature-flags';
15
16
  import { Outcome } from '../../domain';
16
- import { MediaViewerError } from '../../errors';
17
+ import { buildImgErrorDiagnostics, MediaViewerError } from '../../errors';
17
18
  import { InteractiveImg } from './interactive-img';
18
19
  import { BaseViewer } from '../base-viewer';
19
20
  function processedFileStateToMediaItem(file) {
@@ -36,8 +37,14 @@ export var ImageViewer = /*#__PURE__*/function (_BaseViewer) {
36
37
  _this.props.onLoad();
37
38
  _this.onMediaDisplayed();
38
39
  });
39
- _defineProperty(_this, "onImgError", function () {
40
- _this.props.onError(new MediaViewerError('imageviewer-src-onerror'));
40
+ _defineProperty(_this, "onImgError", function (event) {
41
+ var secondaryError;
42
+ try {
43
+ secondaryError = buildImgErrorDiagnostics(_this.props.item, event);
44
+ } catch (_unused) {
45
+ secondaryError = undefined;
46
+ }
47
+ _this.props.onError(new MediaViewerError('imageviewer-src-onerror', secondaryError));
41
48
  });
42
49
  return _this;
43
50
  }
@@ -53,7 +60,7 @@ export var ImageViewer = /*#__PURE__*/function (_BaseViewer) {
53
60
  key: "init",
54
61
  value: function () {
55
62
  var _init = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
56
- var _this$props, fileState, mediaClient, collectionName, traceContext, controller, orientation, objectUrl, originalBinaryImageUrl, isLocalFileReference, clientId, preview, _yield$preview, value, origin, item, response, imgError, _t, _t2, _t3;
63
+ var _this$props, fileState, mediaClient, collectionName, traceContext, controller, orientation, objectUrl, originalBinaryImageUrl, isLocalFileReference, clientId, preview, _yield$preview, value, origin, item, response, unsupportedError, imgError, _t, _t2, _t3;
57
64
  return _regeneratorRuntime.wrap(function (_context) {
58
65
  while (1) switch (_context.prev = _context.next) {
59
66
  case 0:
@@ -135,7 +142,11 @@ export var ImageViewer = /*#__PURE__*/function (_BaseViewer) {
135
142
  case 14:
136
143
  if (!(!isLocalFileReference &&
137
144
  // objectUrl at this point is binary file already
138
- !isErrorFileState(fileState) && fileState.status !== 'uploading' && fileState.mediaType === 'image' && isImageMimeTypeSupportedByBrowser(fileState.mimeType))) {
145
+ !isErrorFileState(fileState) && fileState.status !== 'uploading' && fileState.mediaType === 'image')) {
146
+ _context.next = 17;
147
+ break;
148
+ }
149
+ if (!isImageMimeTypeSupportedByBrowser(fileState.mimeType)) {
139
150
  _context.next = 16;
140
151
  break;
141
152
  }
@@ -143,7 +154,25 @@ export var ImageViewer = /*#__PURE__*/function (_BaseViewer) {
143
154
  return mediaClient.file.getFileBinaryURL(fileState.id, collectionName);
144
155
  case 15:
145
156
  originalBinaryImageUrl = _context.sent;
157
+ _context.next = 17;
158
+ break;
146
159
  case 16:
160
+ if (!fg('platform_media_unsupported_mime_routing')) {
161
+ _context.next = 17;
162
+ break;
163
+ }
164
+ // Route unsupported MIME types browser can't natively decode straight to the
165
+ // unsupported/download view instead of handing a guaranteed-undecodable blob
166
+ // to <img>, which would only ever fire onerror and
167
+ // surface as an opaque `imageviewer-src-onerror` failure.
168
+ this.revokeObjectUrl(objectUrl);
169
+ unsupportedError = new MediaViewerError('imageviewer-unsupported-mime');
170
+ this.setState({
171
+ content: Outcome.failed(unsupportedError)
172
+ });
173
+ this.props.onError(unsupportedError);
174
+ return _context.abrupt("return");
175
+ case 17:
147
176
  this.setState({
148
177
  content: Outcome.successful({
149
178
  objectUrl: objectUrl,
@@ -152,10 +181,10 @@ export var ImageViewer = /*#__PURE__*/function (_BaseViewer) {
152
181
  clientId: clientId
153
182
  })
154
183
  });
155
- _context.next = 18;
184
+ _context.next = 19;
156
185
  break;
157
- case 17:
158
- _context.prev = 17;
186
+ case 18:
187
+ _context.prev = 18;
159
188
  _t3 = _context["catch"](2);
160
189
  // TODO : properly handle aborted requests (MS-2843)
161
190
  if (!(controller !== null && controller !== void 0 && controller.signal.aborted)) {
@@ -165,11 +194,11 @@ export var ImageViewer = /*#__PURE__*/function (_BaseViewer) {
165
194
  });
166
195
  this.props.onError(imgError);
167
196
  }
168
- case 18:
197
+ case 19:
169
198
  case "end":
170
199
  return _context.stop();
171
200
  }
172
- }, _callee, this, [[2, 17], [3, 5]]);
201
+ }, _callee, this, [[2, 18], [3, 5]]);
173
202
  }));
174
203
  function init() {
175
204
  return _init.apply(this, arguments);
@@ -26,6 +26,8 @@ export declare const getErrorMessageFromError: (error: MediaViewerError) => Mess
26
26
  export declare class ErrorMessage extends React.Component<Props & WrappedComponentProps & WithAnalyticsEventsProps, {}> {
27
27
  private getErrorInfo;
28
28
  componentDidMount(): void;
29
+ private static readonly previewUnsupportedReasons;
30
+ static isPreviewUnsupported(error: MediaViewerError): boolean;
29
31
  static getEventPayload(error: MediaViewerError, fileId: string, fileState?: FileState, traceContext?: MediaTraceContext): PreviewUnsupportedEventPayload | LoadFailedEventPayload;
30
32
  render(): React.JSX.Element;
31
33
  }
@@ -1,4 +1,5 @@
1
- import { type MediaClientErrorReason, type RequestErrorMetadata } from '@atlaskit/media-client';
1
+ import { type SyntheticEvent } from 'react';
2
+ import { type FileState, type MediaClientErrorReason, type RequestErrorMetadata } from '@atlaskit/media-client';
2
3
  import { type ZipEntry } from 'unzipit';
3
4
  export declare class MediaViewerError extends Error {
4
5
  readonly primaryReason: MediaViewerErrorReason | ArchiveViewerErrorReason;
@@ -13,7 +14,8 @@ export declare class ArchiveViewerError extends MediaViewerError {
13
14
  constructor(primaryReason: ArchiveViewerErrorReason, secondaryError?: Error | undefined, zipEntry?: ZipEntry | undefined);
14
15
  }
15
16
  export declare function isArchiveViewerError(err: Error): err is ArchiveViewerError;
16
- export type MediaViewerErrorReason = 'collection-fetch-metadata' | 'header-fetch-metadata' | 'itemviewer-onerror' | 'itemviewer-fetch-metadata' | 'itemviewer-file-error-status' | 'itemviewer-file-failed-processing-status' | 'imageviewer-external-onerror' | 'imageviewer-fetch-url' | 'imageviewer-src-onerror' | 'audioviewer-fetch-url' | 'audioviewer-missing-artefact' | 'audioviewer-playback' | 'videoviewer-fetch-url' | 'videoviewer-missing-artefact' | 'videoviewer-playback' | 'docviewer-fetch-url' | 'docviewer-content-fetch-failed' | 'docviewer-fetch-pdf' | 'codeviewer-fetch-src' | 'codeviewer-load-src' | 'codeviewer-file-size-exceeds' | 'codeviewer-parse-email' | 'svg-img-error' | 'svg-binary-fetch' | 'svg-unknown-error' | 'svg-blob-to-datauri' | 'unsupported' | 'custom-viewer-error' | 'download';
17
+ type srcType = 'blob' | 'remote' | 'data' | 'unknown';
18
+ export type MediaViewerErrorReason = 'collection-fetch-metadata' | 'header-fetch-metadata' | 'itemviewer-onerror' | 'itemviewer-fetch-metadata' | 'itemviewer-file-error-status' | 'itemviewer-file-failed-processing-status' | 'imageviewer-external-onerror' | 'imageviewer-fetch-url' | 'imageviewer-src-onerror' | 'imageviewer-unsupported-mime' | 'audioviewer-fetch-url' | 'audioviewer-missing-artefact' | 'audioviewer-playback' | 'videoviewer-fetch-url' | 'videoviewer-missing-artefact' | 'videoviewer-playback' | 'docviewer-fetch-url' | 'docviewer-content-fetch-failed' | 'docviewer-fetch-pdf' | 'codeviewer-fetch-src' | 'codeviewer-load-src' | 'codeviewer-file-size-exceeds' | 'codeviewer-parse-email' | 'svg-img-error' | 'svg-binary-fetch' | 'svg-unknown-error' | 'svg-blob-to-datauri' | 'unsupported' | 'custom-viewer-error' | 'download';
17
19
  export type ArchiveViewerErrorReason = 'archiveviewer-bundle-loader' | 'archiveviewer-read-binary' | 'archiveviewer-not-zip' | 'archiveviewer-create-url' | 'archiveviewer-imageviewer-onerror' | 'archiveviewer-videoviewer-onerror' | 'archiveviewer-audioviewer-onerror' | 'archiveviewer-docviewer-onerror' | 'archiveviewer-codeviewer-onerror' | 'archiveviewer-codeviewer-file-size-exceeds' | 'archiveviewer-missing-name-src' | 'archiveviewer-unsupported' | 'archiveviewer-encrypted-entry' | 'archiveviewer-customrenderer-onerror';
18
20
  export type PrimaryErrorReason = MediaViewerErrorReason | ArchiveViewerErrorReason;
19
21
  export type SecondaryErrorReason = MediaClientErrorReason | 'unknown' | 'nativeError' | undefined;
@@ -21,3 +23,23 @@ export declare function getPrimaryErrorReason(error: MediaViewerError): PrimaryE
21
23
  export declare function getSecondaryErrorReason(error: MediaViewerError): SecondaryErrorReason;
22
24
  export declare function getErrorDetail(error?: MediaViewerError): string;
23
25
  export declare function getRequestMetadata(error?: MediaViewerError): RequestErrorMetadata | undefined;
26
+ /**
27
+ * Classifies the failed `<img>` source into a coarse, non-sensitive enum so we can
28
+ * tell which kind of source failed without leaking the (potentially signed) url.
29
+ */
30
+ export declare function classifyFailedSrc(currentSrc?: string): srcType;
31
+ /**
32
+ * Builds a descriptive `secondaryError` for an `<img>` onerror so the downstream
33
+ * `loadFailed` analytics event reports a meaningful `error`/`errorDetail` instead of
34
+ * the opaque `unknown`/`unknown`. The returned Error's `message` captures decode
35
+ * diagnostics as a comma-separated `key=value` string (undefined fields are omitted):
36
+ * - whether the browser decoded any pixels (`naturalWidth`/`naturalHeight === 0`)
37
+ * - the file MIME type and whether it is natively browser-decodable
38
+ * - which source failed (preview blob vs. original-binary/HD url)
39
+ *
40
+ * @example
41
+ * // message:
42
+ * "mimeType=image/png, isBrowserDecodable=true, naturalWidth=0, naturalHeight=0, failedSrcType=blob"
43
+ */
44
+ export declare function buildImgErrorDiagnostics(item: FileState | undefined, event?: SyntheticEvent<HTMLImageElement, Event>): Error;
45
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/media-viewer",
3
- "version": "54.3.8",
3
+ "version": "54.5.0",
4
4
  "description": "MediaViewer is Atlassian's powerful solution for viewing files on the web. It's both powerful and extendable yet easy-to-integrate",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -12,9 +12,7 @@
12
12
  "module": "dist/esm/index.js",
13
13
  "module:es2019": "dist/es2019/index.js",
14
14
  "types": "dist/types/index.d.ts",
15
- "sideEffects": [
16
- "*.compiled.css"
17
- ],
15
+ "sideEffects": ["*.compiled.css"],
18
16
  "atlaskit:src": "src/index.ts",
19
17
  "atlassian": {
20
18
  "react-compiler": {
@@ -35,27 +33,27 @@
35
33
  "@atlaskit/code": "^18.2.0",
36
34
  "@atlaskit/css": "^1.0.0",
37
35
  "@atlaskit/form": "^16.1.0",
38
- "@atlaskit/heading": "^6.1.0",
36
+ "@atlaskit/heading": "^6.2.0",
39
37
  "@atlaskit/icon": "^36.2.0",
40
38
  "@atlaskit/icon-file-type": "^8.1.0",
41
39
  "@atlaskit/icon-lab": "^7.3.0",
42
40
  "@atlaskit/media-client": "^37.2.0",
43
41
  "@atlaskit/media-client-react": "^6.1.0",
44
- "@atlaskit/media-common": "^14.2.0",
42
+ "@atlaskit/media-common": "^14.3.0",
45
43
  "@atlaskit/media-document-viewer": "^1.1.0",
46
44
  "@atlaskit/media-state": "^3.0.0",
47
45
  "@atlaskit/media-svg": "^3.1.0",
48
- "@atlaskit/media-ui": "^30.5.0",
46
+ "@atlaskit/media-ui": "^30.6.0",
49
47
  "@atlaskit/platform-feature-flags": "^2.0.0",
50
48
  "@atlaskit/portal": "^6.1.0",
51
- "@atlaskit/primitives": "^20.3.0",
49
+ "@atlaskit/primitives": "^20.5.0",
52
50
  "@atlaskit/react-compiler-gating": "^0.2.0",
53
51
  "@atlaskit/side-navigation": "^12.2.0",
54
52
  "@atlaskit/spinner": "^20.1.0",
55
53
  "@atlaskit/textfield": "^9.1.0",
56
54
  "@atlaskit/theme": "^26.1.0",
57
- "@atlaskit/tmp-editor-statsig": "^121.1.0",
58
- "@atlaskit/tokens": "^15.4.0",
55
+ "@atlaskit/tmp-editor-statsig": "^121.3.0",
56
+ "@atlaskit/tokens": "^15.5.0",
59
57
  "@atlaskit/tooltip": "^23.1.0",
60
58
  "@atlaskit/ufo": "^1.0.0",
61
59
  "@babel/runtime": "^7.0.0",
@@ -89,12 +87,12 @@
89
87
  "@atlaskit/media-core": "^38.0.0",
90
88
  "@atlaskit/media-integration-test-helpers": "workspace:^",
91
89
  "@atlaskit/media-test-data": "^4.0.0",
92
- "@atlaskit/media-test-helpers": "^42.1.0",
90
+ "@atlaskit/media-test-helpers": "^42.2.0",
93
91
  "@atlaskit/ssr": "workspace:^",
94
92
  "@atlaskit/toggle": "^17.1.0",
95
93
  "@atlassian/a11y-jest-testing": "^0.13.0",
96
94
  "@atlassian/feature-flags-test-utils": "^1.1.0",
97
- "@atlassian/testing-library": "^0.9.0",
95
+ "@atlassian/testing-library": "^0.10.0",
98
96
  "@atlassian/ufo": "^0.8.0",
99
97
  "@testing-library/dom": "^10.1.0",
100
98
  "@testing-library/react": "^16.3.0",
@@ -143,14 +141,14 @@
143
141
  },
144
142
  "platform_media_archive_zip_guard": {
145
143
  "type": "boolean"
144
+ },
145
+ "platform_media_unsupported_mime_routing": {
146
+ "type": "boolean"
146
147
  }
147
148
  },
148
149
  "techstack": {
149
150
  "@repo/internal": {
150
- "design-tokens": [
151
- "color",
152
- "spacing"
153
- ]
151
+ "design-tokens": ["color", "spacing"]
154
152
  }
155
153
  }
156
154
  }