@sequent-org/moodboard 1.4.6 → 1.4.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sequent-org/moodboard",
3
- "version": "1.4.6",
3
+ "version": "1.4.7",
4
4
  "type": "module",
5
5
  "description": "Interactive moodboard",
6
6
  "main": "./src/index.js",
@@ -1,3 +1,5 @@
1
+ import { isV2ImageDownloadUrl } from '../services/AssetUrlPolicy.js';
2
+
1
3
  // src/core/ApiClient.js
2
4
  export class ApiClient {
3
5
  constructor(baseUrl, authToken = null) {
@@ -159,6 +161,12 @@ export class ApiClient {
159
161
  if (hasForbiddenInlineSrc) {
160
162
  throw new Error(`Image object "${obj.id || 'unknown'}" contains forbidden data/blob src. Save is blocked.`);
161
163
  }
164
+ if (topSrc && !isV2ImageDownloadUrl(topSrc)) {
165
+ throw new Error(`Image object "${obj.id || 'unknown'}" has non-v2 src URL. Save is blocked.`);
166
+ }
167
+ if (propSrc && !isV2ImageDownloadUrl(propSrc)) {
168
+ throw new Error(`Image object "${obj.id || 'unknown'}" has non-v2 properties.src URL. Save is blocked.`);
169
+ }
162
170
 
163
171
  const cleanedObj = { ...obj };
164
172
 
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { Events } from './events/Events.js';
5
5
  import { logMindmapCompoundDebug } from '../mindmap/MindmapCompoundContract.js';
6
+ import { isV2ImageDownloadUrl } from '../services/AssetUrlPolicy.js';
6
7
  export class SaveManager {
7
8
  constructor(eventBus, options = {}) {
8
9
  this.eventBus = eventBus;
@@ -245,6 +246,12 @@ export class SaveManager {
245
246
  if (/^data:/i.test(topSrc) || /^blob:/i.test(topSrc) || /^data:/i.test(propSrc) || /^blob:/i.test(propSrc)) {
246
247
  throw new Error(`Image object "${obj.id || 'unknown'}" contains forbidden data/blob src. Save is blocked.`);
247
248
  }
249
+ if (topSrc && !isV2ImageDownloadUrl(topSrc)) {
250
+ throw new Error(`Image object "${obj.id || 'unknown'}" has non-v2 src URL. Save is blocked.`);
251
+ }
252
+ if (propSrc && !isV2ImageDownloadUrl(propSrc)) {
253
+ throw new Error(`Image object "${obj.id || 'unknown'}" has non-v2 properties.src URL. Save is blocked.`);
254
+ }
248
255
  }
249
256
  }
250
257
 
@@ -1,6 +1,7 @@
1
1
  import { Events } from '../events/Events.js';
2
2
  import { PasteObjectCommand } from '../commands/index.js';
3
3
  import { RevitScreenshotMetadataService } from '../../services/RevitScreenshotMetadataService.js';
4
+ import { isV2ImageDownloadUrl } from '../../services/AssetUrlPolicy.js';
4
5
 
5
6
  export function setupClipboardFlow(core) {
6
7
  const revitMetadataService = new RevitScreenshotMetadataService(console);
@@ -21,7 +22,12 @@ export function setupClipboardFlow(core) {
21
22
 
22
23
  const ensureServerImage = async ({ src, name, imageId }) => {
23
24
  if (imageId) {
24
- return { src, name, imageId };
25
+ const serverUrl = typeof src === 'string' ? src.trim() : '';
26
+ if (!isV2ImageDownloadUrl(serverUrl)) {
27
+ alert('Некорректный адрес изображения. Изображение не добавлено.');
28
+ return null;
29
+ }
30
+ return { src: serverUrl, name, imageId };
25
31
  }
26
32
  if (!core.imageUploadService) {
27
33
  alert('Сервис загрузки изображений недоступен. Изображение не добавлено.');
@@ -39,8 +45,12 @@ export function setupClipboardFlow(core) {
39
45
  const blob = await response.blob();
40
46
  uploadResult = await core.imageUploadService.uploadImage(blob, name || 'clipboard-image');
41
47
  }
48
+ const serverUrl = typeof uploadResult.url === 'string' ? uploadResult.url.trim() : '';
49
+ if (!isV2ImageDownloadUrl(serverUrl)) {
50
+ throw new Error('Сервер вернул некорректный URL изображения');
51
+ }
42
52
  return {
43
- src: uploadResult.url,
53
+ src: serverUrl,
44
54
  name: uploadResult.name || name,
45
55
  imageId: uploadResult.imageId || uploadResult.id
46
56
  };
@@ -272,10 +282,12 @@ export function setupClipboardFlow(core) {
272
282
  const img = new Image();
273
283
  img.decoding = 'async';
274
284
  img.onload = () => { void placeWithAspect(img.naturalWidth || 0, img.naturalHeight || 0); };
275
- img.onerror = () => { void placeWithAspect(0, 0); };
285
+ img.onerror = () => {
286
+ alert('Не удалось загрузить изображение с сервера. Изображение не добавлено.');
287
+ };
276
288
  img.src = uploaded.src;
277
289
  } catch (_) {
278
- void placeWithAspect(0, 0);
290
+ alert('Не удалось загрузить изображение с сервера. Изображение не добавлено.');
279
291
  }
280
292
  });
281
293
 
@@ -320,10 +332,12 @@ export function setupClipboardFlow(core) {
320
332
  const img = new Image();
321
333
  img.decoding = 'async';
322
334
  img.onload = () => { void placeWithAspect(img.naturalWidth || 0, img.naturalHeight || 0); };
323
- img.onerror = () => { void placeWithAspect(0, 0); };
335
+ img.onerror = () => {
336
+ alert('Не удалось загрузить изображение с сервера. Изображение не добавлено.');
337
+ };
324
338
  img.src = uploaded.src;
325
339
  } catch (_) {
326
- void placeWithAspect(0, 0);
340
+ alert('Не удалось загрузить изображение с сервера. Изображение не добавлено.');
327
341
  }
328
342
  });
329
343
 
@@ -25,8 +25,6 @@ export function setupSaveFlow(core) {
25
25
  core.eventBus.on(Events.Save.Success, async () => {
26
26
  if (typeof core.revealPendingObjectsAfterSave === 'function') {
27
27
  core.revealPendingObjectsAfterSave();
28
- } else if (typeof core.revealPendingImageObjectsAfterSave === 'function') {
29
- core.revealPendingImageObjectsAfterSave();
30
28
  }
31
29
  // ВРЕМЕННО ОТКЛЮЧЕНО:
32
30
  // cleanup-фича требует доработки контракта и серверной поддержки.
package/src/core/index.js CHANGED
@@ -465,11 +465,6 @@ export class CoreMoodBoard {
465
465
  this._pendingPersistAckVisibilityIds.clear();
466
466
  }
467
467
 
468
- // Backward-compat alias for tests/integrations created in previous step.
469
- revealPendingImageObjectsAfterSave() {
470
- this.revealPendingObjectsAfterSave();
471
- }
472
-
473
468
  // === Прикрепления к фреймам ===
474
469
  // Логика фреймов перенесена в FrameService
475
470
 
@@ -21,12 +21,10 @@ function resolveMoodboardApiBase(board) {
21
21
  const raw = String(board?.options?.apiUrl || '').trim();
22
22
  if (!raw) return '/api/v2/moodboard';
23
23
 
24
- // Совместимость с legacy конфигом: /api/moodboard -> /api/v2/moodboard
25
- if (raw.endsWith('/api/moodboard')) {
26
- return raw.replace(/\/api\/moodboard$/, '/api/v2/moodboard');
27
- }
28
- if (raw.endsWith('/api/moodboard/')) {
29
- return raw.replace(/\/api\/moodboard\/$/, '/api/v2/moodboard/');
24
+ const hasLegacyPath = /\/api\/moodboard(?:\/|$)/i.test(raw);
25
+ const hasV2Path = /\/api\/v2\/moodboard(?:\/|$)/i.test(raw);
26
+ if (hasLegacyPath && !hasV2Path) {
27
+ throw new Error('Legacy apiUrl "/api/moodboard" is not supported. Use "/api/v2/moodboard".');
30
28
  }
31
29
 
32
30
  return raw;
@@ -0,0 +1,8 @@
1
+ export function isV2ImageDownloadUrl(url) {
2
+ return typeof url === 'string' && /^\/api\/v2\/images\/[^/]+\/download$/i.test(url.trim());
3
+ }
4
+
5
+ export function isV2FileDownloadUrl(url) {
6
+ return typeof url === 'string' && /^\/api\/v2\/files\/[^/]+\/download$/i.test(url.trim());
7
+ }
8
+
@@ -1,3 +1,5 @@
1
+ import { isV2FileDownloadUrl } from './AssetUrlPolicy.js';
2
+
1
3
  /**
2
4
  * Сервис для загрузки и управления файлами на сервере
3
5
  */
@@ -92,10 +94,19 @@ export class FileUploadService {
92
94
  throw new Error(result.message || 'Ошибка загрузки файла');
93
95
  }
94
96
 
97
+ const fileId = result.data.fileId || result.data.id;
98
+ const serverUrl = typeof result.data.url === 'string' ? result.data.url.trim() : '';
99
+ if (!fileId) {
100
+ throw new Error('Сервер не вернул fileId.');
101
+ }
102
+ if (!isV2FileDownloadUrl(serverUrl)) {
103
+ throw new Error('Некорректный URL файла от сервера. Ожидается /api/v2/files/{fileId}/download');
104
+ }
105
+
95
106
  return {
96
- id: result.data.fileId || result.data.id, // Используем fileId как основное поле, id для обратной совместимости
97
- fileId: result.data.fileId || result.data.id, // Добавляем fileId для явного доступа
98
- url: result.data.url,
107
+ id: fileId, // Используем fileId как основное поле, id для обратной совместимости
108
+ fileId, // Добавляем fileId для явного доступа
109
+ url: serverUrl,
99
110
  size: result.data.size,
100
111
  name: result.data.name,
101
112
  type: result.data.type
@@ -1,3 +1,5 @@
1
+ import { isV2ImageDownloadUrl } from './AssetUrlPolicy.js';
2
+
1
3
  /**
2
4
  * Сервис для загрузки и управления изображениями на сервере
3
5
  */
@@ -97,10 +99,19 @@ export class ImageUploadService {
97
99
  throw new Error(result.message || 'Ошибка загрузки изображения');
98
100
  }
99
101
 
102
+ const imageId = result.data.imageId || result.data.id;
103
+ const serverUrl = typeof result.data.url === 'string' ? result.data.url.trim() : '';
104
+ if (!imageId) {
105
+ throw new Error('Сервер не вернул imageId.');
106
+ }
107
+ if (!isV2ImageDownloadUrl(serverUrl)) {
108
+ throw new Error('Некорректный URL изображения от сервера. Ожидается /api/v2/images/{imageId}/download');
109
+ }
110
+
100
111
  return {
101
- id: result.data.imageId || result.data.id, // Используем imageId как основное поле, id для обратной совместимости
102
- imageId: result.data.imageId || result.data.id, // Добавляем imageId для явного доступа
103
- url: result.data.url,
112
+ id: imageId, // Используем imageId как основное поле, id для обратной совместимости
113
+ imageId, // Добавляем imageId для явного доступа
114
+ url: serverUrl,
104
115
  width: result.data.width,
105
116
  height: result.data.height,
106
117
  name: result.data.name,