@atlaskit/media-client 36.3.1 → 36.3.3

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 ADDED
@@ -0,0 +1,51 @@
1
+ # media-client — Agent Guide
2
+
3
+ > **Parent guide:** Always read [`../AGENTS.md`](../AGENTS.md) before working in this package — it
4
+ > contains workspace-wide rules covering platform guidelines, changeset workflow, accessibility
5
+ > standards, and the knowledge index protocol that applies to every Media package.
6
+
7
+ ## Package identity
8
+
9
+ | Field | Value |
10
+ |---|---|
11
+ | NPM name | `@atlaskit/media-client` |
12
+ | Path | `media-client/` |
13
+ | Owner | Media Exif |
14
+ | Purpose | Media API Web Client Library — manages file state subscriptions, uploads, downloads, and communication with the Media backend API |
15
+
16
+ ## Knowledge index
17
+
18
+ > Read, search, edit, investigate, and validate the repository knowledge index at
19
+ > `.agents/knowledge-index/`. ALWAYS use this skill before making ANY repo change, or when
20
+ > exploring the codebase. The knowledge index is the canonical starting point for every coding task
21
+ > in this repo, trigger this skill aggressively whenever the user mentions architecture, conventions,
22
+ > packages, modules, services, build, coding, writing code, APIs, feature flags, testing, or
23
+ > anything similar. Also trigger when "is the knowledge index up to date?" / "update the knowledge
24
+ > index" / "what does the knowledge index say about Y?" — even if they don't say the words
25
+ > "knowledge index". Also use it (via the `validate` subcommand) to detect drift between the
26
+ > codebase and the docs after any code change. Use the `generate` subcommand to bootstrap the index
27
+ > in a new repository.
28
+
29
+ **Entry point:** `.agents/knowledge-index/INDEX.md`
30
+ **Unit doc:** `.agents/knowledge-index/domains/media/units/atlaskit-media-client.md`
31
+ **CLI:** `python3 .agents/skills/knowledge-index/scripts/kg.py {find,read,edit,investigate,validate,init,explore,generate} …`
32
+ **Refresh protocol:** never silently bump `Last verified` — always re-read the listed `Sources` first, edit, then `kg.py edit <path> --message "<reason>"`.
33
+
34
+ ## Key source files
35
+
36
+ - `src/index.ts` — public API entry point
37
+ - `src/client/` — MediaClient implementation and file fetcher
38
+ - `src/models/` — data models and error types
39
+ - `src/uploader/` — file upload logic
40
+ - `src/utils/` — utilities (hashing, polling, request, mobile upload state machine)
41
+ - `src/upload-controller.ts` — upload lifecycle controller
42
+ - `src/file-streams-cache.ts` — observable file state caching
43
+ - `src/identifier.ts` — file/external identifier types
44
+
45
+ ## Development notes
46
+
47
+ - Peer dependencies: `@atlaskit/media-core`, `@atlaskit/media-state`
48
+ - Key internal dependencies: `@atlaskit/chunkinator`, `@atlaskit/media-common`
49
+ - 8 feature flags registered (see `package.json` → `platform-feature-flags`)
50
+ - Notable flags: `platform_media_cdn_delivery`, `platform_media_cdn_single_host`, `platform_media_auth_provider_analytics`
51
+ - All new behaviour changes must be behind a feature gate (`fg()` from `@atlaskit/platform-feature-flags`)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @atlaskit/media-client
2
2
 
3
+ ## 36.3.3
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+
9
+ ## 36.3.2
10
+
11
+ ### Patch Changes
12
+
13
+ - [`a68f551856a81`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/a68f551856a81) -
14
+ Add initialFileState to GetFileOptions interface to support SSR-seeded file state.
15
+ - Updated dependencies
16
+
3
17
  ## 36.3.1
4
18
 
5
19
  ### Patch Changes
@@ -87,8 +87,8 @@ var FileFetcherImpl = exports.FileFetcherImpl = /*#__PURE__*/function () {
87
87
  });
88
88
  });
89
89
  // TODO: ----- ADD TICKET TO PASS TRACE ID to this.dataloader.load
90
- (0, _defineProperty2.default)(this, "createDownloadFileStream", function (id, collectionName, occurrenceKey, includeHashForDuplicateFiles, forceRefresh) {
91
- var subject = (0, _createMediaSubject.createMediaSubject)();
90
+ (0, _defineProperty2.default)(this, "createDownloadFileStream", function (id, collectionName, occurrenceKey, includeHashForDuplicateFiles, forceRefresh, initialFileState) {
91
+ var subject = (0, _createMediaSubject.createMediaSubject)(initialFileState);
92
92
  var poll = new _polling.PollingFunction();
93
93
 
94
94
  // ensure subject errors if polling exceeds max iterations or uncaught exception in executor
@@ -100,6 +100,13 @@ var FileFetcherImpl = exports.FileFetcherImpl = /*#__PURE__*/function () {
100
100
  return _regenerator.default.wrap(function (_context) {
101
101
  while (1) switch (_context.prev = _context.next) {
102
102
  case 0:
103
+ if (!(!forceRefresh && (initialFileState === null || initialFileState === void 0 ? void 0 : initialFileState.status) === 'processed')) {
104
+ _context.next = 1;
105
+ break;
106
+ }
107
+ subject.complete();
108
+ return _context.abrupt("return");
109
+ case 1:
103
110
  if (forceRefresh) {
104
111
  _this.dataloader.clear({
105
112
  id: id,
@@ -107,16 +114,16 @@ var FileFetcherImpl = exports.FileFetcherImpl = /*#__PURE__*/function () {
107
114
  includeHashForDuplicateFiles: includeHashForDuplicateFiles
108
115
  });
109
116
  }
110
- _context.next = 1;
117
+ _context.next = 2;
111
118
  return _this.dataloader.load({
112
119
  id: id,
113
120
  collectionName: collectionName,
114
121
  includeHashForDuplicateFiles: includeHashForDuplicateFiles
115
122
  });
116
- case 1:
123
+ case 2:
117
124
  response = _context.sent;
118
125
  if (!(0, _media.isNotFoundMediaItemDetails)(response)) {
119
- _context.next = 2;
126
+ _context.next = 3;
120
127
  break;
121
128
  }
122
129
  throw new _error.FileFetcherError('emptyItems', {
@@ -125,9 +132,9 @@ var FileFetcherImpl = exports.FileFetcherImpl = /*#__PURE__*/function () {
125
132
  occurrenceKey: occurrenceKey,
126
133
  traceContext: response.metadataTraceContext
127
134
  });
128
- case 2:
135
+ case 3:
129
136
  if (!(0, _detectEmptyFile.isEmptyFile)(response)) {
130
- _context.next = 3;
137
+ _context.next = 4;
131
138
  break;
132
139
  }
133
140
  throw new _error.FileFetcherError('zeroVersionFile', {
@@ -136,20 +143,20 @@ var FileFetcherImpl = exports.FileFetcherImpl = /*#__PURE__*/function () {
136
143
  occurrenceKey: occurrenceKey,
137
144
  traceContext: response.metadataTraceContext
138
145
  });
139
- case 3:
146
+ case 4:
140
147
  fileState = (0, _fileState2.mapMediaItemToFileState)(id, response);
141
148
  subject.next(fileState);
142
149
  _t = fileState.status;
143
- _context.next = _t === 'processing' ? 4 : _t === 'processed' ? 5 : 6;
150
+ _context.next = _t === 'processing' ? 5 : _t === 'processed' ? 6 : 7;
144
151
  break;
145
- case 4:
152
+ case 5:
146
153
  // the only case for continuing polling, otherwise this function is run once only
147
154
  poll.next();
148
- return _context.abrupt("continue", 6);
149
- case 5:
150
- subject.complete();
151
- return _context.abrupt("continue", 6);
155
+ return _context.abrupt("continue", 7);
152
156
  case 6:
157
+ subject.complete();
158
+ return _context.abrupt("continue", 7);
159
+ case 7:
153
160
  case "end":
154
161
  return _context.stop();
155
162
  }
@@ -464,8 +471,12 @@ var FileFetcherImpl = exports.FileFetcherImpl = /*#__PURE__*/function () {
464
471
  var collectionName = options.collectionName,
465
472
  occurrenceKey = options.occurrenceKey,
466
473
  includeHashForDuplicateFiles = options.includeHashForDuplicateFiles,
467
- forceRefresh = options.forceRefresh;
474
+ forceRefresh = options.forceRefresh,
475
+ initialFileState = options.initialFileState;
468
476
  if (!(0, _isValidUuid.isValidUuid)(id)) {
477
+ // Do NOT pass initialFileState here — for an invalid UUID the SSR seed
478
+ // is meaningless and would cause subscribers to briefly see a
479
+ // valid-looking file state before the error is emitted.
469
480
  var subject = (0, _createMediaSubject.createMediaSubject)();
470
481
  var err = new _error.FileFetcherError('invalidFileId', {
471
482
  id: id,
@@ -481,7 +492,7 @@ var FileFetcherImpl = exports.FileFetcherImpl = /*#__PURE__*/function () {
481
492
  (0, _fileStreamsCache.getFileStreamsCache)().remove(id);
482
493
  }
483
494
  return (0, _mediaSubscribable.fromObservable)((0, _fileStreamsCache.getFileStreamsCache)().getOrInsert(id, function () {
484
- var subject = _this2.createDownloadFileStream(id, collectionName, undefined, includeHashForDuplicateFiles, forceRefresh);
495
+ var subject = _this2.createDownloadFileStream(id, collectionName, undefined, includeHashForDuplicateFiles, forceRefresh, initialFileState);
485
496
  subject.subscribe({
486
497
  next: function next(fileState) {
487
498
  _this2.setFileState(id, fileState);
@@ -57,7 +57,8 @@ var mapMediaFileToFileState = exports.mapMediaFileToFileState = function mapMedi
57
57
  hash = _mediaFile$data.hash,
58
58
  abuseClassification = _mediaFile$data.abuseClassification,
59
59
  mediaMetadata = _mediaFile$data.mediaMetadata,
60
- failReason = _mediaFile$data.failReason;
60
+ failReason = _mediaFile$data.failReason,
61
+ previewCdnUrl = _mediaFile$data.previewCdnUrl;
61
62
  var baseState = {
62
63
  id: id,
63
64
  name: name,
@@ -70,7 +71,8 @@ var mapMediaFileToFileState = exports.mapMediaFileToFileState = function mapMedi
70
71
  hash: hash,
71
72
  metadataTraceContext: metadataTraceContext,
72
73
  abuseClassification: abuseClassification,
73
- mediaMetadata: mediaMetadata
74
+ mediaMetadata: mediaMetadata,
75
+ previewCdnUrl: previewCdnUrl
74
76
  };
75
77
  switch (processingStatus) {
76
78
  case 'pending':
@@ -57,13 +57,17 @@ export class FileFetcherImpl {
57
57
  });
58
58
  });
59
59
  // TODO: ----- ADD TICKET TO PASS TRACE ID to this.dataloader.load
60
- _defineProperty(this, "createDownloadFileStream", (id, collectionName, occurrenceKey, includeHashForDuplicateFiles, forceRefresh) => {
61
- const subject = createMediaSubject();
60
+ _defineProperty(this, "createDownloadFileStream", (id, collectionName, occurrenceKey, includeHashForDuplicateFiles, forceRefresh, initialFileState) => {
61
+ const subject = createMediaSubject(initialFileState);
62
62
  const poll = new PollingFunction();
63
63
 
64
64
  // ensure subject errors if polling exceeds max iterations or uncaught exception in executor
65
65
  poll.onError = error => subject.error(error);
66
66
  poll.execute(async () => {
67
+ if (!forceRefresh && (initialFileState === null || initialFileState === void 0 ? void 0 : initialFileState.status) === 'processed') {
68
+ subject.complete();
69
+ return;
70
+ }
67
71
  if (forceRefresh) {
68
72
  this.dataloader.clear({
69
73
  id,
@@ -275,9 +279,13 @@ export class FileFetcherImpl {
275
279
  collectionName,
276
280
  occurrenceKey,
277
281
  includeHashForDuplicateFiles,
278
- forceRefresh
282
+ forceRefresh,
283
+ initialFileState
279
284
  } = options;
280
285
  if (!isValidUuid(id)) {
286
+ // Do NOT pass initialFileState here — for an invalid UUID the SSR seed
287
+ // is meaningless and would cause subscribers to briefly see a
288
+ // valid-looking file state before the error is emitted.
281
289
  const subject = createMediaSubject();
282
290
  const err = new FileFetcherError('invalidFileId', {
283
291
  id,
@@ -293,7 +301,7 @@ export class FileFetcherImpl {
293
301
  getFileStreamsCache().remove(id);
294
302
  }
295
303
  return fromObservable(getFileStreamsCache().getOrInsert(id, () => {
296
- const subject = this.createDownloadFileStream(id, collectionName, undefined, includeHashForDuplicateFiles, forceRefresh);
304
+ const subject = this.createDownloadFileStream(id, collectionName, undefined, includeHashForDuplicateFiles, forceRefresh, initialFileState);
297
305
  subject.subscribe({
298
306
  next: fileState => {
299
307
  this.setFileState(id, fileState);
@@ -31,7 +31,8 @@ export const mapMediaFileToFileState = mediaFile => {
31
31
  hash,
32
32
  abuseClassification,
33
33
  mediaMetadata,
34
- failReason
34
+ failReason,
35
+ previewCdnUrl
35
36
  } = mediaFile.data;
36
37
  const baseState = {
37
38
  id,
@@ -45,7 +46,8 @@ export const mapMediaFileToFileState = mediaFile => {
45
46
  hash,
46
47
  metadataTraceContext,
47
48
  abuseClassification,
48
- mediaMetadata
49
+ mediaMetadata,
50
+ previewCdnUrl
49
51
  };
50
52
  switch (processingStatus) {
51
53
  case 'pending':
@@ -70,8 +70,8 @@ export var FileFetcherImpl = /*#__PURE__*/function () {
70
70
  });
71
71
  });
72
72
  // TODO: ----- ADD TICKET TO PASS TRACE ID to this.dataloader.load
73
- _defineProperty(this, "createDownloadFileStream", function (id, collectionName, occurrenceKey, includeHashForDuplicateFiles, forceRefresh) {
74
- var subject = createMediaSubject();
73
+ _defineProperty(this, "createDownloadFileStream", function (id, collectionName, occurrenceKey, includeHashForDuplicateFiles, forceRefresh, initialFileState) {
74
+ var subject = createMediaSubject(initialFileState);
75
75
  var poll = new PollingFunction();
76
76
 
77
77
  // ensure subject errors if polling exceeds max iterations or uncaught exception in executor
@@ -83,6 +83,13 @@ export var FileFetcherImpl = /*#__PURE__*/function () {
83
83
  return _regeneratorRuntime.wrap(function (_context) {
84
84
  while (1) switch (_context.prev = _context.next) {
85
85
  case 0:
86
+ if (!(!forceRefresh && (initialFileState === null || initialFileState === void 0 ? void 0 : initialFileState.status) === 'processed')) {
87
+ _context.next = 1;
88
+ break;
89
+ }
90
+ subject.complete();
91
+ return _context.abrupt("return");
92
+ case 1:
86
93
  if (forceRefresh) {
87
94
  _this.dataloader.clear({
88
95
  id: id,
@@ -90,16 +97,16 @@ export var FileFetcherImpl = /*#__PURE__*/function () {
90
97
  includeHashForDuplicateFiles: includeHashForDuplicateFiles
91
98
  });
92
99
  }
93
- _context.next = 1;
100
+ _context.next = 2;
94
101
  return _this.dataloader.load({
95
102
  id: id,
96
103
  collectionName: collectionName,
97
104
  includeHashForDuplicateFiles: includeHashForDuplicateFiles
98
105
  });
99
- case 1:
106
+ case 2:
100
107
  response = _context.sent;
101
108
  if (!isNotFoundMediaItemDetails(response)) {
102
- _context.next = 2;
109
+ _context.next = 3;
103
110
  break;
104
111
  }
105
112
  throw new FileFetcherError('emptyItems', {
@@ -108,9 +115,9 @@ export var FileFetcherImpl = /*#__PURE__*/function () {
108
115
  occurrenceKey: occurrenceKey,
109
116
  traceContext: response.metadataTraceContext
110
117
  });
111
- case 2:
118
+ case 3:
112
119
  if (!isEmptyFile(response)) {
113
- _context.next = 3;
120
+ _context.next = 4;
114
121
  break;
115
122
  }
116
123
  throw new FileFetcherError('zeroVersionFile', {
@@ -119,20 +126,20 @@ export var FileFetcherImpl = /*#__PURE__*/function () {
119
126
  occurrenceKey: occurrenceKey,
120
127
  traceContext: response.metadataTraceContext
121
128
  });
122
- case 3:
129
+ case 4:
123
130
  fileState = mapMediaItemToFileState(id, response);
124
131
  subject.next(fileState);
125
132
  _t = fileState.status;
126
- _context.next = _t === 'processing' ? 4 : _t === 'processed' ? 5 : 6;
133
+ _context.next = _t === 'processing' ? 5 : _t === 'processed' ? 6 : 7;
127
134
  break;
128
- case 4:
135
+ case 5:
129
136
  // the only case for continuing polling, otherwise this function is run once only
130
137
  poll.next();
131
- return _context.abrupt("continue", 6);
132
- case 5:
133
- subject.complete();
134
- return _context.abrupt("continue", 6);
138
+ return _context.abrupt("continue", 7);
135
139
  case 6:
140
+ subject.complete();
141
+ return _context.abrupt("continue", 7);
142
+ case 7:
136
143
  case "end":
137
144
  return _context.stop();
138
145
  }
@@ -447,8 +454,12 @@ export var FileFetcherImpl = /*#__PURE__*/function () {
447
454
  var collectionName = options.collectionName,
448
455
  occurrenceKey = options.occurrenceKey,
449
456
  includeHashForDuplicateFiles = options.includeHashForDuplicateFiles,
450
- forceRefresh = options.forceRefresh;
457
+ forceRefresh = options.forceRefresh,
458
+ initialFileState = options.initialFileState;
451
459
  if (!isValidUuid(id)) {
460
+ // Do NOT pass initialFileState here — for an invalid UUID the SSR seed
461
+ // is meaningless and would cause subscribers to briefly see a
462
+ // valid-looking file state before the error is emitted.
452
463
  var subject = createMediaSubject();
453
464
  var err = new FileFetcherError('invalidFileId', {
454
465
  id: id,
@@ -464,7 +475,7 @@ export var FileFetcherImpl = /*#__PURE__*/function () {
464
475
  getFileStreamsCache().remove(id);
465
476
  }
466
477
  return fromObservable(getFileStreamsCache().getOrInsert(id, function () {
467
- var subject = _this2.createDownloadFileStream(id, collectionName, undefined, includeHashForDuplicateFiles, forceRefresh);
478
+ var subject = _this2.createDownloadFileStream(id, collectionName, undefined, includeHashForDuplicateFiles, forceRefresh, initialFileState);
468
479
  subject.subscribe({
469
480
  next: function next(fileState) {
470
481
  _this2.setFileState(id, fileState);
@@ -50,7 +50,8 @@ export var mapMediaFileToFileState = function mapMediaFileToFileState(mediaFile)
50
50
  hash = _mediaFile$data.hash,
51
51
  abuseClassification = _mediaFile$data.abuseClassification,
52
52
  mediaMetadata = _mediaFile$data.mediaMetadata,
53
- failReason = _mediaFile$data.failReason;
53
+ failReason = _mediaFile$data.failReason,
54
+ previewCdnUrl = _mediaFile$data.previewCdnUrl;
54
55
  var baseState = {
55
56
  id: id,
56
57
  name: name,
@@ -63,7 +64,8 @@ export var mapMediaFileToFileState = function mapMediaFileToFileState(mediaFile)
63
64
  hash: hash,
64
65
  metadataTraceContext: metadataTraceContext,
65
66
  abuseClassification: abuseClassification,
66
- mediaMetadata: mediaMetadata
67
+ mediaMetadata: mediaMetadata,
68
+ previewCdnUrl: previewCdnUrl
67
69
  };
68
70
  switch (processingStatus) {
69
71
  case 'pending':
@@ -6,6 +6,7 @@ export type FileStatus = CommonFileStatus;
6
6
  export interface PreviewOptions {
7
7
  }
8
8
  export interface GetFileOptions {
9
+ initialFileState?: FileState;
9
10
  preview?: PreviewOptions;
10
11
  collectionName?: string;
11
12
  occurrenceKey?: string;
@@ -25,6 +25,7 @@ export type MediaFile = {
25
25
  readonly abuseClassification?: AbuseClassification;
26
26
  readonly mediaMetadata?: FileMediaMetadata;
27
27
  readonly failReason?: ProcessingFailReason;
28
+ readonly previewCdnUrl?: string;
28
29
  };
29
30
  export type MediaItemDetails = {
30
31
  readonly mediaType: MediaType;
@@ -40,6 +41,7 @@ export type MediaItemDetails = {
40
41
  readonly abuseClassification?: AbuseClassification;
41
42
  readonly mediaMetadata?: FileMediaMetadata;
42
43
  readonly failReason?: ProcessingFailReason;
44
+ readonly previewCdnUrl?: string;
43
45
  };
44
46
  export type NotFoundMediaItemDetails = {
45
47
  readonly id: string;
@@ -6,6 +6,7 @@ export type FileStatus = CommonFileStatus;
6
6
  export interface PreviewOptions {
7
7
  }
8
8
  export interface GetFileOptions {
9
+ initialFileState?: FileState;
9
10
  preview?: PreviewOptions;
10
11
  collectionName?: string;
11
12
  occurrenceKey?: string;
@@ -25,6 +25,7 @@ export type MediaFile = {
25
25
  readonly abuseClassification?: AbuseClassification;
26
26
  readonly mediaMetadata?: FileMediaMetadata;
27
27
  readonly failReason?: ProcessingFailReason;
28
+ readonly previewCdnUrl?: string;
28
29
  };
29
30
  export type MediaItemDetails = {
30
31
  readonly mediaType: MediaType;
@@ -40,6 +41,7 @@ export type MediaItemDetails = {
40
41
  readonly abuseClassification?: AbuseClassification;
41
42
  readonly mediaMetadata?: FileMediaMetadata;
42
43
  readonly failReason?: ProcessingFailReason;
44
+ readonly previewCdnUrl?: string;
43
45
  };
44
46
  export type NotFoundMediaItemDetails = {
45
47
  readonly id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/media-client",
3
- "version": "36.3.1",
3
+ "version": "36.3.3",
4
4
  "description": "Media API Web Client Library",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -29,7 +29,7 @@
29
29
  }
30
30
  },
31
31
  "dependencies": {
32
- "@atlaskit/atlassian-context": "^0.9.0",
32
+ "@atlaskit/atlassian-context": "^0.10.0",
33
33
  "@atlaskit/chunkinator": "^7.1.0",
34
34
  "@atlaskit/media-common": "^13.3.0",
35
35
  "@atlaskit/platform-feature-flags": "^1.1.0",
@@ -46,13 +46,13 @@
46
46
  },
47
47
  "peerDependencies": {
48
48
  "@atlaskit/media-core": "^37.1.0",
49
- "@atlaskit/media-state": "^2.1.0"
49
+ "@atlaskit/media-state": "^2.2.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@atlaskit/media-core": "^37.1.0",
53
- "@atlaskit/media-state": "^2.1.0",
53
+ "@atlaskit/media-state": "^2.2.0",
54
54
  "@atlaskit/ssr": "workspace:^",
55
- "@atlaskit/tokens": "^13.0.0",
55
+ "@atlaskit/tokens": "^13.1.0",
56
56
  "@atlassian/a11y-jest-testing": "^0.11.0",
57
57
  "@atlassian/feature-flags-test-utils": "^1.1.0",
58
58
  "@emotion/react": "^11.7.1",