@widgetic/editor 4.0.9 → 4.0.11
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/README.md +118 -0
- package/dist/components/WidgetPreview.svelte +65 -27
- package/dist/components/WidgetPreview.svelte.d.ts +8 -0
- package/dist/components/editors/CompositionEditorApp.svelte +508 -0
- package/dist/components/editors/CompositionEditorApp.svelte.d.ts +75 -0
- package/dist/components/editors/PropsEditor.svelte +193 -792
- package/dist/components/editors/PropsEditor.svelte.d.ts +7 -0
- package/dist/components/ic/BrowserInputController.svelte +21 -2
- package/dist/components/ic/ColorPickerInputController.svelte +24 -6
- package/dist/components/ic/DateFieldInputController.svelte +7 -3
- package/dist/components/ic/DatePickerInputController.svelte +10 -4
- package/dist/components/ic/DropdownInputController.svelte +11 -4
- package/dist/components/ic/FontInputController.svelte +68 -44
- package/dist/components/ic/InputControllerHeader.svelte +11 -8
- package/dist/components/ic/NumberInputController.svelte +4 -2
- package/dist/components/ic/PositionInputController.svelte +14 -11
- package/dist/components/ic/RangeCalendarInputController.svelte +10 -4
- package/dist/components/ic/RoundedCornersInputController.svelte +17 -9
- package/dist/components/ic/TextAreaInputController.svelte +20 -7
- package/dist/components/ic/TextAreaPreviewInputController.svelte +19 -69
- package/dist/components/ic/TimeInputController.svelte +9 -3
- package/dist/components/ic/VectorInputController.svelte +80 -54
- package/dist/components/ic/shared/ItemGrid.svelte +5 -1
- package/dist/composition/compositionApi.d.ts +95 -0
- package/dist/composition/compositionApi.js +289 -0
- package/dist/composition/compositionEditorController.d.ts +254 -0
- package/dist/composition/compositionEditorController.js +734 -0
- package/dist/composition/compositionHelpers.d.ts +72 -0
- package/dist/composition/compositionHelpers.js +403 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/mediaLibraryContext.d.ts +10 -0
- package/dist/mediaLibraryContext.js +30 -0
- package/dist/utils/FontManager.d.ts +21 -5
- package/dist/utils/FontManager.js +62 -67
- package/dist/utils/VectorManager.d.ts +0 -1
- package/dist/utils/VectorManager.js +10 -12
- package/dist/utils/editorStorage.d.ts +23 -0
- package/dist/utils/editorStorage.js +95 -0
- package/dist/utils/fontUtils.js +5 -3
- package/dist/utils/htmlSafety.d.ts +21 -0
- package/dist/utils/htmlSafety.js +38 -0
- package/dist/utils/itemReorder.d.ts +90 -0
- package/dist/utils/itemReorder.js +354 -0
- package/dist/utils/recentColors.js +4 -2
- package/dist/utils/richTextPreview.d.ts +38 -0
- package/dist/utils/richTextPreview.js +207 -0
- package/dist/utils/scrollActivity.d.ts +14 -0
- package/dist/utils/scrollActivity.js +63 -0
- package/dist/utils/shadowDom.d.ts +67 -0
- package/dist/utils/shadowDom.js +211 -0
- package/dist/utils/vectorUtils.js +5 -3
- package/dist/utils/visibilityCondition.d.ts +40 -0
- package/dist/utils/visibilityCondition.js +535 -0
- package/package.json +6 -5
|
@@ -66,6 +66,9 @@
|
|
|
66
66
|
} from '../../utils/vectorUtils';
|
|
67
67
|
import type { VectorData } from '../../utils/VectorManager';
|
|
68
68
|
import VectorManager from '../../utils/VectorManager';
|
|
69
|
+
import { escapeHtml } from '../../utils/htmlSafety';
|
|
70
|
+
import { listenForScroll, queryInTreeOf } from '../../utils/shadowDom';
|
|
71
|
+
import { readEditorStorageValue, writeEditorStorageValue } from '../../utils/editorStorage';
|
|
69
72
|
|
|
70
73
|
// Constants
|
|
71
74
|
const VECTOR_FILTER_KEY = (id: string) => `vector-specific-filter-state-${id}`;
|
|
@@ -109,6 +112,8 @@
|
|
|
109
112
|
|
|
110
113
|
// Vector loading optimization
|
|
111
114
|
let vectorListContainer: HTMLElement;
|
|
115
|
+
// The control's own element: it says which tree (the page or a module's shadow root) to listen for scrolling in
|
|
116
|
+
let controllerElement: HTMLDivElement | undefined;
|
|
112
117
|
let vectorObserver = new IntersectionObserver(() => {});
|
|
113
118
|
|
|
114
119
|
// Initialize VectorManager instance
|
|
@@ -175,6 +180,15 @@
|
|
|
175
180
|
`;
|
|
176
181
|
}
|
|
177
182
|
|
|
183
|
+
/**
|
|
184
|
+
* The sticky header of the vector list. It is looked up in the tree the list lives in (inside a module that is a
|
|
185
|
+
* shadow root, which `document.querySelector` cannot see into), and the id is escaped so that one with dots or
|
|
186
|
+
* brackets in it is found too.
|
|
187
|
+
*/
|
|
188
|
+
function findStickyHeader(): Element | null {
|
|
189
|
+
return queryInTreeOf(vectorListContainer, `#${CSS.escape(`vector-header-${controllerId}`)}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
178
192
|
// Function to update scroll state
|
|
179
193
|
function updateScrollState() {
|
|
180
194
|
if (vectorListContainer) {
|
|
@@ -183,7 +197,7 @@
|
|
|
183
197
|
|
|
184
198
|
// If we're transitioning from scrolled to not scrolled, remove transition
|
|
185
199
|
if (wasScrolled && !isScrolled) {
|
|
186
|
-
const stickyHeader =
|
|
200
|
+
const stickyHeader = findStickyHeader();
|
|
187
201
|
if (stickyHeader) {
|
|
188
202
|
stickyHeader.classList.remove('transition-border');
|
|
189
203
|
// Force a reflow to ensure the transition class removal takes effect
|
|
@@ -191,7 +205,7 @@
|
|
|
191
205
|
}
|
|
192
206
|
} else if (!wasScrolled && isScrolled) {
|
|
193
207
|
// Add transition class when starting to scroll
|
|
194
|
-
const stickyHeader =
|
|
208
|
+
const stickyHeader = findStickyHeader();
|
|
195
209
|
if (stickyHeader) {
|
|
196
210
|
stickyHeader.classList.add('transition-border');
|
|
197
211
|
}
|
|
@@ -314,31 +328,14 @@
|
|
|
314
328
|
await handleSearch(event);
|
|
315
329
|
}, 300); // 300ms debounce time
|
|
316
330
|
|
|
317
|
-
//
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const vectorName = event.detail;
|
|
321
|
-
// Check if we can get the vector URL from allVectors
|
|
322
|
-
const vectorData = allVectors.find(v => v.name === vectorName);
|
|
323
|
-
const vectorUrl = vectorData?.url || 'unknown';
|
|
324
|
-
|
|
325
|
-
console.error(`❌ [VectorInput] Failed to load SVG: "${vectorName}" from URL: ${vectorUrl}`);
|
|
326
|
-
svgLoadErrors.set(vectorName, true);
|
|
327
|
-
// Force component update
|
|
328
|
-
svgLoadErrors = new Map(svgLoadErrors);
|
|
329
|
-
}
|
|
330
|
-
};
|
|
331
|
-
|
|
332
|
-
// Add the event listener
|
|
333
|
-
document.addEventListener('vector-load-error', vectorErrorHandler);
|
|
334
|
-
|
|
335
|
-
// Store the handler reference for cleanup in onDestroy
|
|
336
|
-
(window as any)['vectorErrorHandler'] = vectorErrorHandler;
|
|
331
|
+
// SVG loading errors are noticed by the watchVectorImageErrors action on the list of pictures. They used to be
|
|
332
|
+
// announced on `document` by an inline onerror handler and caught here; with the editor inside a partner's page
|
|
333
|
+
// that was an event on the partner's document and a handler stored on the partner's `window`.
|
|
337
334
|
|
|
338
335
|
try {
|
|
339
336
|
if (browser) {
|
|
340
337
|
// Load saved filter
|
|
341
|
-
const savedFilter =
|
|
338
|
+
const savedFilter = readEditorStorageValue(VECTOR_FILTER_KEY(controllerId));
|
|
342
339
|
if (savedFilter) {
|
|
343
340
|
currentFilter = savedFilter as FilterType;
|
|
344
341
|
}
|
|
@@ -493,7 +490,7 @@
|
|
|
493
490
|
lastActiveFilter = currentFilter;
|
|
494
491
|
currentFilter = 'All'; // Reset to All when starting a search
|
|
495
492
|
if (browser) {
|
|
496
|
-
|
|
493
|
+
writeEditorStorageValue(VECTOR_FILTER_KEY(controllerId), 'All');
|
|
497
494
|
}
|
|
498
495
|
}
|
|
499
496
|
|
|
@@ -970,16 +967,7 @@
|
|
|
970
967
|
}
|
|
971
968
|
|
|
972
969
|
onDestroy(() => {
|
|
973
|
-
|
|
974
|
-
window.removeEventListener('touchstart', () => {});
|
|
975
|
-
window.removeEventListener('touchmove', () => {});
|
|
976
|
-
|
|
977
|
-
// Remove vector load error listener using the stored reference
|
|
978
|
-
const vectorErrorHandler = (window as any)['vectorErrorHandler'];
|
|
979
|
-
if (vectorErrorHandler) {
|
|
980
|
-
document.removeEventListener('vector-load-error', vectorErrorHandler);
|
|
981
|
-
delete (window as any)['vectorErrorHandler'];
|
|
982
|
-
}
|
|
970
|
+
stopListeningForScroll();
|
|
983
971
|
|
|
984
972
|
if (vectorObserver) {
|
|
985
973
|
vectorObserver.disconnect();
|
|
@@ -1030,7 +1018,7 @@
|
|
|
1030
1018
|
lastActiveFilter = filter; // Remember the last active filter
|
|
1031
1019
|
|
|
1032
1020
|
if (browser) {
|
|
1033
|
-
|
|
1021
|
+
writeEditorStorageValue(VECTOR_FILTER_KEY(controllerId), filter);
|
|
1034
1022
|
}
|
|
1035
1023
|
|
|
1036
1024
|
if (vectorManagerInstance) {
|
|
@@ -1104,25 +1092,26 @@
|
|
|
1104
1092
|
}
|
|
1105
1093
|
|
|
1106
1094
|
/**
|
|
1107
|
-
* Highlights search term matches in vector names
|
|
1095
|
+
* Highlights search term matches in vector names.
|
|
1096
|
+
* The result is meant for {@html}, so every piece of the name is escaped as it is put in.
|
|
1108
1097
|
*/
|
|
1109
1098
|
function highlightMatch(name: string, term: string) {
|
|
1110
|
-
if (!term) return name;
|
|
1099
|
+
if (!term) return escapeHtml(name);
|
|
1111
1100
|
const nameLower = name.toLowerCase();
|
|
1112
1101
|
const termLower = term.toLowerCase().trim();
|
|
1113
1102
|
|
|
1114
1103
|
// If the name contains the search term, highlight that part in blue
|
|
1115
1104
|
if (termLower && nameLower.includes(termLower)) {
|
|
1116
1105
|
const index = nameLower.indexOf(termLower);
|
|
1117
|
-
const beforeMatch = name.slice(0, index);
|
|
1118
|
-
const match = name.slice(index, index + termLower.length);
|
|
1119
|
-
const afterMatch = name.slice(index + termLower.length);
|
|
1106
|
+
const beforeMatch = escapeHtml(name.slice(0, index));
|
|
1107
|
+
const match = escapeHtml(name.slice(index, index + termLower.length));
|
|
1108
|
+
const afterMatch = escapeHtml(name.slice(index + termLower.length));
|
|
1120
1109
|
|
|
1121
1110
|
return `<span style="color: hsl(var(--grey7))">${beforeMatch}<span style="color: #45BCFE">${match}</span>${afterMatch}</span>`;
|
|
1122
1111
|
}
|
|
1123
1112
|
|
|
1124
1113
|
// Default case - just return the name in grey7 color
|
|
1125
|
-
return `<span style="color: hsl(var(--grey7))">${name}</span>`;
|
|
1114
|
+
return `<span style="color: hsl(var(--grey7))">${escapeHtml(name)}</span>`;
|
|
1126
1115
|
}
|
|
1127
1116
|
|
|
1128
1117
|
|
|
@@ -1173,15 +1162,16 @@
|
|
|
1173
1162
|
}
|
|
1174
1163
|
}
|
|
1175
1164
|
|
|
1176
|
-
// Add scroll listener with passive option
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1165
|
+
// Add scroll listener with passive option. It listens to the page and, inside a module, to the module's own panels
|
|
1166
|
+
// (a scroll never leaves a shadow root, so a listener on the window alone does not hear about those).
|
|
1167
|
+
let stopListeningForScroll: () => void = () => {};
|
|
1168
|
+
onMount(() => {
|
|
1169
|
+
stopListeningForScroll = listenForScroll(controllerElement, handleGlobalScroll, { passive: true });
|
|
1180
1170
|
});
|
|
1181
1171
|
|
|
1182
|
-
//
|
|
1183
|
-
|
|
1184
|
-
|
|
1172
|
+
// (Two touchstart/touchmove listeners that did nothing used to be added to the window here, and could never be
|
|
1173
|
+
// removed: each input left two more on the customer's page every time it was shown. They did nothing, so they are
|
|
1174
|
+
// gone.)
|
|
1185
1175
|
|
|
1186
1176
|
// Add to VectorInputController.svelte
|
|
1187
1177
|
function handleToggle() {
|
|
@@ -1299,6 +1289,37 @@
|
|
|
1299
1289
|
removeVector(vector.name);
|
|
1300
1290
|
}
|
|
1301
1291
|
|
|
1292
|
+
/**
|
|
1293
|
+
* Svelte action for the list that holds the vector pictures: notices a picture that does not load and shows the
|
|
1294
|
+
* "Failed to load" state for that vector. `error` events do not bubble, so it listens in the capture phase; a
|
|
1295
|
+
* picture is found by its data-vector-name (see renderVectorItem). It replaces an inline onerror attribute that
|
|
1296
|
+
* reported through `document`, which is the partner's page when the editor runs inside one.
|
|
1297
|
+
*/
|
|
1298
|
+
function watchVectorImageErrors(node: HTMLElement) {
|
|
1299
|
+
const onImageError = (event: Event) => {
|
|
1300
|
+
const image = event.target;
|
|
1301
|
+
if (!(image instanceof HTMLImageElement)) return;
|
|
1302
|
+
const vectorName = image.dataset.vectorName;
|
|
1303
|
+
if (!vectorName) return;
|
|
1304
|
+
image.style.display = 'none';
|
|
1305
|
+
// Reported once per vector, however many pictures of it are on screen.
|
|
1306
|
+
if (hasSvgLoadError(vectorName)) return;
|
|
1307
|
+
const vectorData = allVectors.find(vector => vector.name === vectorName);
|
|
1308
|
+
console.error(`❌ [VectorInput] Failed to load SVG: "${vectorName}" from URL: ${vectorData?.url || image.src || 'unknown'}`);
|
|
1309
|
+
svgLoadErrors.set(vectorName, true);
|
|
1310
|
+
// Force component update
|
|
1311
|
+
svgLoadErrors = new Map(svgLoadErrors);
|
|
1312
|
+
};
|
|
1313
|
+
|
|
1314
|
+
node.addEventListener('error', onImageError, true);
|
|
1315
|
+
|
|
1316
|
+
return {
|
|
1317
|
+
destroy() {
|
|
1318
|
+
node.removeEventListener('error', onImageError, true);
|
|
1319
|
+
}
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1302
1323
|
// Create a Svelte action to handle image error events
|
|
1303
1324
|
function handleImageError(node: HTMLElement, vectorName: string) {
|
|
1304
1325
|
const errorHandler = (event: Event) => {
|
|
@@ -1345,15 +1366,19 @@
|
|
|
1345
1366
|
};
|
|
1346
1367
|
}
|
|
1347
1368
|
|
|
1369
|
+
// The result is used as HTML by RecentItemsSection and ItemGrid: the name and the address of a vector (a custom
|
|
1370
|
+
// one is named after the person's own file) are escaped as they are put in. A picture that does not load is
|
|
1371
|
+
// noticed by watchVectorImageErrors, which finds it by data-vector-name; an inline onerror="..." would not run
|
|
1372
|
+
// under a strict Content-Security-Policy.
|
|
1348
1373
|
return {
|
|
1349
1374
|
html: `
|
|
1350
1375
|
<div class="flex items-center justify-center w-full h-full vector-img-container">
|
|
1351
1376
|
<img
|
|
1352
|
-
src="${vector.url}"
|
|
1353
|
-
alt="${vector.name}"
|
|
1377
|
+
src="${escapeHtml(vector.url)}"
|
|
1378
|
+
alt="${escapeHtml(vector.name)}"
|
|
1379
|
+
data-vector-name="${escapeHtml(vector.name)}"
|
|
1354
1380
|
class="w-[72px] h-[72px] object-contain"
|
|
1355
1381
|
draggable="false"
|
|
1356
|
-
onerror="this.style.display='none'; document.dispatchEvent(new CustomEvent('vector-load-error', {detail: this.alt, bubbles: true})); console.error('❌ [VectorInput] Vector failed to load:', this.alt, 'URL:', this.src);"
|
|
1357
1382
|
/>
|
|
1358
1383
|
</div>
|
|
1359
1384
|
`
|
|
@@ -1399,7 +1424,7 @@
|
|
|
1399
1424
|
lastActiveFilter = currentFilter;
|
|
1400
1425
|
currentFilter = 'All'; // Reset to All when starting a search
|
|
1401
1426
|
if (browser) {
|
|
1402
|
-
|
|
1427
|
+
writeEditorStorageValue(VECTOR_FILTER_KEY(controllerId), 'All');
|
|
1403
1428
|
}
|
|
1404
1429
|
}
|
|
1405
1430
|
|
|
@@ -1472,7 +1497,7 @@
|
|
|
1472
1497
|
}
|
|
1473
1498
|
</script>
|
|
1474
1499
|
|
|
1475
|
-
<div class="flex flex-col gap-2 widgetic-colors">
|
|
1500
|
+
<div bind:this={controllerElement} class="flex flex-col gap-2 widgetic-colors">
|
|
1476
1501
|
<div class="flex items-center w-full relative">
|
|
1477
1502
|
<div class="w-[185px] flex-shrink-0 ml-auto">
|
|
1478
1503
|
<InputControllerHeader
|
|
@@ -1617,7 +1642,7 @@
|
|
|
1617
1642
|
// Restore the previous filter when clearing search
|
|
1618
1643
|
currentFilter = lastActiveFilter;
|
|
1619
1644
|
if (browser) {
|
|
1620
|
-
|
|
1645
|
+
writeEditorStorageValue(VECTOR_FILTER_KEY(controllerId), lastActiveFilter);
|
|
1621
1646
|
}
|
|
1622
1647
|
|
|
1623
1648
|
// Reset batch index to start fresh
|
|
@@ -1691,6 +1716,7 @@
|
|
|
1691
1716
|
<div
|
|
1692
1717
|
class="flex-1 {filteredVectors.length > 0 ? 'overflow-y-auto' : 'overflow-hidden'} min-h-0 vector-list-container"
|
|
1693
1718
|
bind:this={vectorListContainer}
|
|
1719
|
+
use:watchVectorImageErrors
|
|
1694
1720
|
onscroll={(e: Event) => {
|
|
1695
1721
|
e.stopPropagation();
|
|
1696
1722
|
handleVectorListScroll(e);
|
|
@@ -8,9 +8,13 @@
|
|
|
8
8
|
* - Handles item selection
|
|
9
9
|
* - Supports custom item rendering
|
|
10
10
|
* - Optimized with intersection observer
|
|
11
|
+
*
|
|
12
|
+
* `renderItem` may return a plain string (shown as text) or `{ html }`. The `html` is put on the page as it is, so the
|
|
13
|
+
* function that makes it must escape everything that comes from outside (names, addresses): see escapeHtml.
|
|
11
14
|
-->
|
|
12
15
|
<script lang="ts">
|
|
13
16
|
import { Check } from '@widgetic/design-system/icons';
|
|
17
|
+
import { escapeHtml } from '../../../utils/htmlSafety';
|
|
14
18
|
|
|
15
19
|
export let items: any[] = [];
|
|
16
20
|
export let containerRef: (node: HTMLElement) => void;
|
|
@@ -41,7 +45,7 @@
|
|
|
41
45
|
function renderContent(item: any) {
|
|
42
46
|
const rendered = renderItem(item);
|
|
43
47
|
if (typeof rendered === 'string') {
|
|
44
|
-
return `<span>${rendered}</span>`;
|
|
48
|
+
return `<span>${escapeHtml(rendered)}</span>`;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
if (rendered && typeof rendered === 'object' && 'html' in rendered) {
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The calls an embedded composition editor makes to the Widgetic API, over `fetch`.
|
|
3
|
+
*
|
|
4
|
+
* Why not the generated SDK: the editor runs in a customer's browser and may only call a short list of routes
|
|
5
|
+
* (those an editor session allows), the list is fixed, and it must work with whatever token the host page gives it.
|
|
6
|
+
* A client of its own keeps the editor small and independent of the SDK's release cycle, and it keeps the few
|
|
7
|
+
* rules that matter in one place:
|
|
8
|
+
*
|
|
9
|
+
* - the token is asked from the host page before each request (`getAccessToken('initial')`); when the API
|
|
10
|
+
* answers 401 it is asked again (`'expired'`) and the request is sent once more with a token that is new;
|
|
11
|
+
* - a save carries an `Idempotency-Key` (the API requires one on every PUT from a session), kept the same when
|
|
12
|
+
* the request is sent again after a 401, so one save is never applied twice;
|
|
13
|
+
* - only the fields editing changes can be sent (`SaveCompositionBody`); a session is refused any other;
|
|
14
|
+
* - every failure is a `CompositionApiError` with the API's own code, so the host can tell an ended session
|
|
15
|
+
* (`EDITOR_SESSION_EXPIRED`...) from a missing composition (`NOT_FOUND`) from a widget that was never
|
|
16
|
+
* published (`WIDGET_NOT_PUBLISHED`);
|
|
17
|
+
* - a request that gets no answer does not hang the editor: it fails with `TIMEOUT` after a set time.
|
|
18
|
+
*/
|
|
19
|
+
import { type JsonRecord } from './compositionHelpers.js';
|
|
20
|
+
/** Why the token is being asked for. `expired` means the API just refused the previous one. */
|
|
21
|
+
export type TokenReason = 'initial' | 'expired';
|
|
22
|
+
/** The fields of a composition an editor may change. Anything else is refused by the API for an editor session. */
|
|
23
|
+
export interface SaveCompositionBody {
|
|
24
|
+
name?: string;
|
|
25
|
+
content?: JsonRecord;
|
|
26
|
+
design?: JsonRecord;
|
|
27
|
+
embedSettings?: JsonRecord;
|
|
28
|
+
}
|
|
29
|
+
/** Where to show a composition: the widget build the preview frame loads (`GET /compositions/{id}/preview`). */
|
|
30
|
+
export interface CompositionPreviewInfo {
|
|
31
|
+
compositionId: string;
|
|
32
|
+
widgetId: string;
|
|
33
|
+
version: number;
|
|
34
|
+
previewUrl: string;
|
|
35
|
+
}
|
|
36
|
+
export interface CompositionApiOptions {
|
|
37
|
+
/** The API's address including the version, like `https://api.widgetic.com/v1`. */
|
|
38
|
+
apiBasePath: string;
|
|
39
|
+
/**
|
|
40
|
+
* Gives the token to call the API with. Called before every request, so a host that keeps its token fresh is
|
|
41
|
+
* always used up to date; called again with `'expired'` when the API answers 401, after which the request is
|
|
42
|
+
* sent once more if (and only if) the token that comes back is a different one. That second call also gets the
|
|
43
|
+
* token that was refused, so a host can tell the refusal of its current token (get a new one) from the late
|
|
44
|
+
* refusal of one it has already replaced (hand out the new one) when several requests are refused together.
|
|
45
|
+
*/
|
|
46
|
+
getAccessToken: (reason: TokenReason, refusedToken?: string) => Promise<string> | string;
|
|
47
|
+
/** The `fetch` to use. Default: the page's own. */
|
|
48
|
+
fetch?: typeof fetch;
|
|
49
|
+
/** Makes the key a save is identified by (8 to 50 letters, digits, dashes and underscores). Default: a random UUID. */
|
|
50
|
+
createIdempotencyKey?: () => string;
|
|
51
|
+
/** How long to wait for an answer before giving up with `TIMEOUT`, in milliseconds. Default: 30 seconds. */
|
|
52
|
+
requestTimeoutMs?: number;
|
|
53
|
+
}
|
|
54
|
+
/** Options of one call. */
|
|
55
|
+
export interface CompositionRequestOptions {
|
|
56
|
+
/** Cancels the request: the call then fails with `ABORTED`. */
|
|
57
|
+
signal?: AbortSignal;
|
|
58
|
+
}
|
|
59
|
+
export interface SaveCompositionOptions extends CompositionRequestOptions {
|
|
60
|
+
/** Identifies this save. Give the same key to send the same save again; default: a new one. */
|
|
61
|
+
idempotencyKey?: string;
|
|
62
|
+
}
|
|
63
|
+
export declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
64
|
+
/** Codes the client itself reports (the API's own codes, such as `NOT_FOUND`, are passed through as they are). */
|
|
65
|
+
export type ClientErrorCode = 'NETWORK' | 'TIMEOUT' | 'ABORTED' | 'TOKEN_UNAVAILABLE' | 'INVALID_RESPONSE';
|
|
66
|
+
/** A request to the API that did not give the answer wanted. */
|
|
67
|
+
export declare class CompositionApiError extends Error {
|
|
68
|
+
/** The HTTP status; `0` when no answer arrived (network, timeout, abort, or no token to send). */
|
|
69
|
+
readonly status: number;
|
|
70
|
+
/** The API's error code (`NOT_FOUND`, `EDITOR_SESSION_EXPIRED`...), or one of {@link ClientErrorCode}. */
|
|
71
|
+
readonly code: string;
|
|
72
|
+
/** The API's correlation id for the failed request, to quote in a support request. */
|
|
73
|
+
readonly correlationId?: string;
|
|
74
|
+
constructor(message: string, details: {
|
|
75
|
+
status: number;
|
|
76
|
+
code: string;
|
|
77
|
+
correlationId?: string;
|
|
78
|
+
});
|
|
79
|
+
/** True when the editor session cannot be used any more: the host page has to give a new one. */
|
|
80
|
+
get sessionEnded(): boolean;
|
|
81
|
+
/** True when sending the same request again later may work: nothing was refused, the network or the server failed. */
|
|
82
|
+
get retryable(): boolean;
|
|
83
|
+
}
|
|
84
|
+
/** What a client of the composition routes can do. */
|
|
85
|
+
export interface CompositionApi {
|
|
86
|
+
getComposition(compositionId: string, options?: CompositionRequestOptions): Promise<JsonRecord>;
|
|
87
|
+
getPreview(compositionId: string, options?: CompositionRequestOptions): Promise<CompositionPreviewInfo>;
|
|
88
|
+
getContentSchema(compositionId: string, options?: CompositionRequestOptions): Promise<unknown>;
|
|
89
|
+
getDesignSchema(compositionId: string, options?: CompositionRequestOptions): Promise<unknown>;
|
|
90
|
+
saveComposition(compositionId: string, body: SaveCompositionBody, options?: SaveCompositionOptions): Promise<JsonRecord>;
|
|
91
|
+
}
|
|
92
|
+
/** A key that is unique, not secret: the page's `crypto.randomUUID`, else random bytes, else the clock and `Math.random`. */
|
|
93
|
+
export declare function createRandomIdempotencyKey(): string;
|
|
94
|
+
/** Builds the client. `apiBasePath` and `getAccessToken` are required. */
|
|
95
|
+
export declare function createCompositionApi(options: CompositionApiOptions): CompositionApi;
|