@atlaskit/media-client 39.1.4 → 39.1.6

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/AGENTS.md CHANGED
@@ -49,8 +49,8 @@ first, edit, then `kg.py edit <path> --message "<reason>"`.
49
49
 
50
50
  - Peer dependencies: `@atlaskit/media-core`, `@atlaskit/media-state`
51
51
  - Key internal dependencies: `@atlaskit/chunkinator`, `@atlaskit/media-common`
52
- - 8 feature flags registered (see `package.json` → `platform-feature-flags`)
52
+ - 10 feature flags registered (see `package.json` → `platform-feature-flags`)
53
53
  - Notable flags: `platform_media_cdn_delivery`, `platform_media_cdn_single_host`,
54
- `platform_media_auth_provider_analytics`
54
+ `platform_media_auth_provider_analytics`, `platform_media_unique_external_upload_name`
55
55
  - All new behaviour changes must be behind a feature gate (`fg()` from
56
56
  `@atlaskit/platform-feature-flags`)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @atlaskit/media-client
2
2
 
3
+ ## 39.1.6
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 39.1.5
10
+
11
+ ### Patch Changes
12
+
13
+ - [`0d4a7d7340b59`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/0d4a7d7340b59) -
14
+ `uploadExternal` now derives a collision-resistant filename for external uploads, behind the
15
+ `platform_media_unique_external_upload_name` feature gate. Endpoint-style source URLs such as
16
+ `/binary` previously produced an identical filename for every image, so publishing collapsed the
17
+ copied page attachments into several versions of a single attachment. The generated name keeps the
18
+ source extension when the URL supplies one and otherwise derives it from the response MIME type.
19
+ Anonymised uploads are unchanged.
20
+
3
21
  ## 39.1.4
4
22
 
5
23
  ### Patch Changes
@@ -22,6 +22,7 @@ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/
22
22
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
23
23
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
24
24
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
25
+ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
25
26
  var _mime = require("mime");
26
27
  var _map = require("rxjs/operators/map");
27
28
  var _Subscription = require("rxjs/Subscription");
@@ -32,6 +33,7 @@ var _mediaTypeUtils = require("@atlaskit/media-common/mediaTypeUtils");
32
33
  var _authToOwner = require("@atlaskit/media-core/auth-to-owner");
33
34
  var _mediaStore = require("@atlaskit/media-state/media-store");
34
35
  var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
36
+ var _fg = require("@atlaskit/platform-feature-flags/fg");
35
37
  var _constants = require("../../constants");
36
38
  var _fileStreamsCache = require("../../file-streams-cache");
37
39
  var _globalMediaEventEmitter = require("../../globalMediaEventEmitter");
@@ -74,6 +76,36 @@ var isCopySourceFileWithToken = function isCopySourceFileWithToken(token) {
74
76
  var isCopyDestinationWithToken = function isCopyDestinationWithToken(token) {
75
77
  return !!token.authProvider;
76
78
  };
79
+ var EXTERNAL_UPLOAD_FALLBACK_STEM = 'image';
80
+ // Only used to resolve relative urls so their path can be read; never requested.
81
+ var EXTERNAL_UPLOAD_URL_BASE = 'https://media.invalid';
82
+
83
+ // A leading dot names a file rather than an extension, so `.gitignore` splits to a stem.
84
+ var splitExtension = function splitExtension(name) {
85
+ var index = name.lastIndexOf('.');
86
+ return index > 0 ? [name.slice(0, index), name.slice(index)] : [name, ''];
87
+ };
88
+
89
+ // Publishing copies media into page attachments keyed by filename, and same-named files collapse
90
+ // into versions of a single attachment. External URLs are frequently endpoint-style (for example
91
+ // `/binary`), so the stem is made unique before the upload reaches Media Services.
92
+ var buildExternalUploadName = function buildExternalUploadName(url, anonymizeFilename) {
93
+ if (anonymizeFilename) {
94
+ return crypto.randomUUID();
95
+ }
96
+ if (!(0, _fg.fg)('platform_media_unique_external_upload_name')) {
97
+ return url.split('/').pop() || '';
98
+ }
99
+
100
+ // Reading the path drops any query string, which would otherwise be captured as part of the
101
+ // extension and suppress the MIME-derived fallback.
102
+ var urlName = new URL(url, EXTERNAL_UPLOAD_URL_BASE).pathname.split('/').pop() || '';
103
+ var _splitExtension = splitExtension(urlName),
104
+ _splitExtension2 = (0, _slicedToArray2.default)(_splitExtension, 2),
105
+ stem = _splitExtension2[0],
106
+ extension = _splitExtension2[1];
107
+ return "".concat(stem || EXTERNAL_UPLOAD_FALLBACK_STEM, "-").concat(crypto.randomUUID()).concat(extension);
108
+ };
77
109
  var FileFetcherImpl = exports.FileFetcherImpl = /*#__PURE__*/function () {
78
110
  function FileFetcherImpl(mediaApi) {
79
111
  var _this = this;
@@ -613,7 +645,7 @@ var FileFetcherImpl = exports.FileFetcherImpl = /*#__PURE__*/function () {
613
645
  return _ref0.apply(this, arguments);
614
646
  };
615
647
  }());
616
- initialName = anonymizeFilename ? crypto.randomUUID() : url.split('/').pop() || ''; // we create a initial fileState with the minimum info that we have at this point
648
+ initialName = buildExternalUploadName(url, anonymizeFilename); // we create a initial fileState with the minimum info that we have at this point
617
649
  fileState = {
618
650
  status: 'processing',
619
651
  name: initialName,
@@ -630,7 +662,7 @@ var FileFetcherImpl = exports.FileFetcherImpl = /*#__PURE__*/function () {
630
662
  this.setFileState(id, fileState);
631
663
  return _context0.abrupt("return", new Promise( /*#__PURE__*/function () {
632
664
  var _ref1 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee9(resolve, reject) {
633
- var blob, type, size, extension, name, file, mediaType, dimensions, _t4;
665
+ var blob, type, size, extensionAllowed, _splitExtension3, _splitExtension4, existingExtension, extension, name, file, mediaType, dimensions, _t4;
634
666
  return _regenerator.default.wrap(function (_context9) {
635
667
  while (1) switch (_context9.prev = _context9.next) {
636
668
  case 0:
@@ -645,7 +677,9 @@ var FileFetcherImpl = exports.FileFetcherImpl = /*#__PURE__*/function () {
645
677
  return _context9.abrupt("return", reject('Could not download remote file'));
646
678
  case 2:
647
679
  type = blob.type, size = blob.size;
648
- extension = anonymizeFilename && (0, _isExperimentEnabled.isExperimentEnabled)('cc_maui_polish_changes_batch_8') ? (0, _mime.getExtension)(type) : undefined;
680
+ extensionAllowed = anonymizeFilename ? (0, _isExperimentEnabled.isExperimentEnabled)('cc_maui_polish_changes_batch_8') : (0, _fg.fg)('platform_media_unique_external_upload_name');
681
+ _splitExtension3 = splitExtension(initialName), _splitExtension4 = (0, _slicedToArray2.default)(_splitExtension3, 2), existingExtension = _splitExtension4[1];
682
+ extension = extensionAllowed && !existingExtension ? (0, _mime.getExtension)(type) : undefined;
649
683
  name = extension ? "".concat(initialName, ".").concat(extension) : initialName;
650
684
  file = {
651
685
  content: blob,
@@ -12,6 +12,7 @@ import { getMediaTypeFromMimeType } from '@atlaskit/media-common/mediaTypeUtils'
12
12
  import { authToOwner } from '@atlaskit/media-core/auth-to-owner';
13
13
  import { mediaStore } from '@atlaskit/media-state/media-store';
14
14
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
15
+ import { fg } from '@atlaskit/platform-feature-flags/fg';
15
16
  import { RECENTS_COLLECTION } from '../../constants';
16
17
  import { getFileStreamsCache } from '../../file-streams-cache';
17
18
  import { globalMediaEventEmitter } from '../../globalMediaEventEmitter';
@@ -50,6 +51,33 @@ export { FileFetcherError } from './FileFetcherError';
50
51
  export { isFileFetcherError } from './isFileFetcherError';
51
52
  const isCopySourceFileWithToken = token => !!token.authProvider;
52
53
  const isCopyDestinationWithToken = token => !!token.authProvider;
54
+ const EXTERNAL_UPLOAD_FALLBACK_STEM = 'image';
55
+ // Only used to resolve relative urls so their path can be read; never requested.
56
+ const EXTERNAL_UPLOAD_URL_BASE = 'https://media.invalid';
57
+
58
+ // A leading dot names a file rather than an extension, so `.gitignore` splits to a stem.
59
+ const splitExtension = name => {
60
+ const index = name.lastIndexOf('.');
61
+ return index > 0 ? [name.slice(0, index), name.slice(index)] : [name, ''];
62
+ };
63
+
64
+ // Publishing copies media into page attachments keyed by filename, and same-named files collapse
65
+ // into versions of a single attachment. External URLs are frequently endpoint-style (for example
66
+ // `/binary`), so the stem is made unique before the upload reaches Media Services.
67
+ const buildExternalUploadName = (url, anonymizeFilename) => {
68
+ if (anonymizeFilename) {
69
+ return crypto.randomUUID();
70
+ }
71
+ if (!fg('platform_media_unique_external_upload_name')) {
72
+ return url.split('/').pop() || '';
73
+ }
74
+
75
+ // Reading the path drops any query string, which would otherwise be captured as part of the
76
+ // extension and suppress the MIME-derived fallback.
77
+ const urlName = new URL(url, EXTERNAL_UPLOAD_URL_BASE).pathname.split('/').pop() || '';
78
+ const [stem, extension] = splitExtension(urlName);
79
+ return `${stem || EXTERNAL_UPLOAD_FALLBACK_STEM}-${crypto.randomUUID()}${extension}`;
80
+ };
53
81
  export class FileFetcherImpl {
54
82
  constructor(mediaApi, store = mediaStore) {
55
83
  _defineProperty(this, "getErrorFileState", (error, id, occurrenceKey) => {
@@ -386,7 +414,7 @@ export class FileFetcherImpl {
386
414
  origin: 'remote'
387
415
  });
388
416
  });
389
- const initialName = anonymizeFilename ? crypto.randomUUID() : url.split('/').pop() || '';
417
+ const initialName = buildExternalUploadName(url, anonymizeFilename);
390
418
  // we create a initial fileState with the minimum info that we have at this point
391
419
  const fileState = {
392
420
  status: 'processing',
@@ -411,7 +439,9 @@ export class FileFetcherImpl {
411
439
  type,
412
440
  size
413
441
  } = blob;
414
- const extension = anonymizeFilename && isExperimentEnabled('cc_maui_polish_changes_batch_8') ? getExtension(type) : undefined;
442
+ const extensionAllowed = anonymizeFilename ? isExperimentEnabled('cc_maui_polish_changes_batch_8') : fg('platform_media_unique_external_upload_name');
443
+ const [, existingExtension] = splitExtension(initialName);
444
+ const extension = extensionAllowed && !existingExtension ? getExtension(type) : undefined;
415
445
  const name = extension ? `${initialName}.${extension}` : initialName;
416
446
  const file = {
417
447
  content: blob,
@@ -2,6 +2,7 @@ import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
2
2
  import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
3
3
  import _createClass from "@babel/runtime/helpers/createClass";
4
4
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
5
+ import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
5
6
  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; }
6
7
  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; }
7
8
  import _regeneratorRuntime from "@babel/runtime/regenerator";
@@ -18,6 +19,7 @@ import { getMediaTypeFromMimeType } from '@atlaskit/media-common/mediaTypeUtils'
18
19
  import { authToOwner } from '@atlaskit/media-core/auth-to-owner';
19
20
  import { mediaStore } from '@atlaskit/media-state/media-store';
20
21
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
22
+ import { fg } from '@atlaskit/platform-feature-flags/fg';
21
23
  import { RECENTS_COLLECTION } from '../../constants';
22
24
  import { getFileStreamsCache } from '../../file-streams-cache';
23
25
  import { globalMediaEventEmitter } from '../../globalMediaEventEmitter';
@@ -60,6 +62,36 @@ var isCopySourceFileWithToken = function isCopySourceFileWithToken(token) {
60
62
  var isCopyDestinationWithToken = function isCopyDestinationWithToken(token) {
61
63
  return !!token.authProvider;
62
64
  };
65
+ var EXTERNAL_UPLOAD_FALLBACK_STEM = 'image';
66
+ // Only used to resolve relative urls so their path can be read; never requested.
67
+ var EXTERNAL_UPLOAD_URL_BASE = 'https://media.invalid';
68
+
69
+ // A leading dot names a file rather than an extension, so `.gitignore` splits to a stem.
70
+ var splitExtension = function splitExtension(name) {
71
+ var index = name.lastIndexOf('.');
72
+ return index > 0 ? [name.slice(0, index), name.slice(index)] : [name, ''];
73
+ };
74
+
75
+ // Publishing copies media into page attachments keyed by filename, and same-named files collapse
76
+ // into versions of a single attachment. External URLs are frequently endpoint-style (for example
77
+ // `/binary`), so the stem is made unique before the upload reaches Media Services.
78
+ var buildExternalUploadName = function buildExternalUploadName(url, anonymizeFilename) {
79
+ if (anonymizeFilename) {
80
+ return crypto.randomUUID();
81
+ }
82
+ if (!fg('platform_media_unique_external_upload_name')) {
83
+ return url.split('/').pop() || '';
84
+ }
85
+
86
+ // Reading the path drops any query string, which would otherwise be captured as part of the
87
+ // extension and suppress the MIME-derived fallback.
88
+ var urlName = new URL(url, EXTERNAL_UPLOAD_URL_BASE).pathname.split('/').pop() || '';
89
+ var _splitExtension = splitExtension(urlName),
90
+ _splitExtension2 = _slicedToArray(_splitExtension, 2),
91
+ stem = _splitExtension2[0],
92
+ extension = _splitExtension2[1];
93
+ return "".concat(stem || EXTERNAL_UPLOAD_FALLBACK_STEM, "-").concat(crypto.randomUUID()).concat(extension);
94
+ };
63
95
  export var FileFetcherImpl = /*#__PURE__*/function () {
64
96
  function FileFetcherImpl(mediaApi) {
65
97
  var _this = this;
@@ -599,7 +631,7 @@ export var FileFetcherImpl = /*#__PURE__*/function () {
599
631
  return _ref0.apply(this, arguments);
600
632
  };
601
633
  }());
602
- initialName = anonymizeFilename ? crypto.randomUUID() : url.split('/').pop() || ''; // we create a initial fileState with the minimum info that we have at this point
634
+ initialName = buildExternalUploadName(url, anonymizeFilename); // we create a initial fileState with the minimum info that we have at this point
603
635
  fileState = {
604
636
  status: 'processing',
605
637
  name: initialName,
@@ -616,7 +648,7 @@ export var FileFetcherImpl = /*#__PURE__*/function () {
616
648
  this.setFileState(id, fileState);
617
649
  return _context0.abrupt("return", new Promise( /*#__PURE__*/function () {
618
650
  var _ref1 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee9(resolve, reject) {
619
- var blob, type, size, extension, name, file, mediaType, dimensions, _t4;
651
+ var blob, type, size, extensionAllowed, _splitExtension3, _splitExtension4, existingExtension, extension, name, file, mediaType, dimensions, _t4;
620
652
  return _regeneratorRuntime.wrap(function (_context9) {
621
653
  while (1) switch (_context9.prev = _context9.next) {
622
654
  case 0:
@@ -631,7 +663,9 @@ export var FileFetcherImpl = /*#__PURE__*/function () {
631
663
  return _context9.abrupt("return", reject('Could not download remote file'));
632
664
  case 2:
633
665
  type = blob.type, size = blob.size;
634
- extension = anonymizeFilename && isExperimentEnabled('cc_maui_polish_changes_batch_8') ? getExtension(type) : undefined;
666
+ extensionAllowed = anonymizeFilename ? isExperimentEnabled('cc_maui_polish_changes_batch_8') : fg('platform_media_unique_external_upload_name');
667
+ _splitExtension3 = splitExtension(initialName), _splitExtension4 = _slicedToArray(_splitExtension3, 2), existingExtension = _splitExtension4[1];
668
+ extension = extensionAllowed && !existingExtension ? getExtension(type) : undefined;
635
669
  name = extension ? "".concat(initialName, ".").concat(extension) : initialName;
636
670
  file = {
637
671
  content: blob,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/media-client",
3
- "version": "39.1.4",
3
+ "version": "39.1.6",
4
4
  "description": "Media API Web Client Library",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -26,7 +26,7 @@
26
26
  "@atlaskit/media-common": "^14.6.0",
27
27
  "@atlaskit/platform-feature-experiments": "^0.3.0",
28
28
  "@atlaskit/platform-feature-flags": "^2.2.0",
29
- "@atlaskit/tmp-editor-statsig": "^225.0.0",
29
+ "@atlaskit/tmp-editor-statsig": "^226.0.0",
30
30
  "@babel/runtime": "^7.0.0",
31
31
  "dataloader": "^2.1.0",
32
32
  "deep-equal": "^1.0.1",
@@ -87,6 +87,9 @@
87
87
  "platform_media_filereader_error_surfacing": {
88
88
  "type": "boolean",
89
89
  "referenceOnly": true
90
+ },
91
+ "platform_media_unique_external_upload_name": {
92
+ "type": "boolean"
90
93
  }
91
94
  }
92
95
  }