@sap-ux/preview-middleware 1.1.3 → 1.2.1

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.
Files changed (37) hide show
  1. package/README.md +31 -29
  2. package/dist/base/config.d.ts +55 -2
  3. package/dist/base/config.js +158 -26
  4. package/dist/base/flp.d.ts +24 -0
  5. package/dist/base/flp.js +116 -18
  6. package/dist/client/adp/api-handler.js +5 -1
  7. package/dist/client/adp/api-handler.ts +11 -4
  8. package/dist/client/adp/controllers/ControllerExtension.controller.js +95 -35
  9. package/dist/client/adp/controllers/ControllerExtension.controller.ts +121 -42
  10. package/dist/client/adp/extend-controller.ts +1 -0
  11. package/dist/client/adp/quick-actions/common/add-controller-to-page.js +2 -2
  12. package/dist/client/adp/quick-actions/common/add-controller-to-page.ts +2 -2
  13. package/dist/client/adp/quick-actions/fe-v4/create-page-action.js +3 -2
  14. package/dist/client/adp/quick-actions/fe-v4/create-page-action.ts +3 -2
  15. package/dist/client/adp/quick-actions/fe-v4/create-table-action-config-change.js +3 -2
  16. package/dist/client/adp/quick-actions/fe-v4/create-table-action-config-change.ts +3 -2
  17. package/dist/client/adp/ui/ControllerExtension.fragment.xml +33 -6
  18. package/dist/client/adp/utils.js +73 -5
  19. package/dist/client/adp/utils.ts +84 -5
  20. package/dist/client/flp/WorkspaceConnector.js +7 -5
  21. package/dist/client/flp/WorkspaceConnector.ts +7 -5
  22. package/dist/client/flp/common.js +293 -1
  23. package/dist/client/flp/common.ts +344 -1
  24. package/dist/client/flp/sandbox1Init.js +187 -0
  25. package/dist/client/flp/sandbox1Init.ts +174 -0
  26. package/dist/client/flp/sandbox2AfterInit.js +66 -0
  27. package/dist/client/flp/sandbox2AfterInit.ts +63 -0
  28. package/dist/client/flp/sandbox2BeforeInit.js +67 -0
  29. package/dist/client/flp/sandbox2BeforeInit.ts +60 -0
  30. package/dist/client/messagebundle.properties +3 -0
  31. package/dist/types/index.d.ts +21 -0
  32. package/package.json +3 -3
  33. package/templates/flp/cdm.ejs +1 -1
  34. package/templates/flp/sandbox.ejs +1 -1
  35. package/templates/flp/sandbox2.ejs +12 -44
  36. package/dist/client/flp/init.js +0 -465
  37. package/dist/client/flp/init.ts +0 -498
@@ -8,8 +8,10 @@ sap.ui.define(["sap/base/util/merge", "sap/ui/fl/write/api/connectors/ObjectStor
8
8
  const getUi5Version = ___utils_versionjs["getUi5Version"];
9
9
  const isLowerThanMinimalUi5Version = ___utils_versionjs["isLowerThanMinimalUi5Version"];
10
10
  const getAdditionalChangeInfo = ___utils_additional_change_infojs["getAdditionalChangeInfo"];
11
- const baseUrl = document.getElementById('sap-ui-bootstrap')?.dataset.openUxPreviewBaseUrl ?? '';
12
- const changesApiPath = `${baseUrl}${CHANGES_API_PATH_STATIC}`;
11
+ const getChangesApiPath = () => {
12
+ const baseUrl = document.getElementById('sap-ui-bootstrap')?.dataset.openUxPreviewBaseUrl ?? '';
13
+ return `${baseUrl}${CHANGES_API_PATH_STATIC}`;
14
+ };
13
15
  const connector = merge({}, ObjectStorageConnector, {
14
16
  layers: [Layer.VENDOR, Layer.CUSTOMER_BASE],
15
17
  storage: {
@@ -33,7 +35,7 @@ sap.ui.define(["sap/base/util/merge", "sap/ui/fl/write/api/connectors/ObjectStor
33
35
  change,
34
36
  additionalChangeInfo
35
37
  };
36
- return fetch(changesApiPath, {
38
+ return fetch(getChangesApiPath(), {
37
39
  method: 'POST',
38
40
  body: JSON.stringify(body, null, 2),
39
41
  headers: {
@@ -49,7 +51,7 @@ sap.ui.define(["sap/base/util/merge", "sap/ui/fl/write/api/connectors/ObjectStor
49
51
  // exceptions in the listener call are ignored
50
52
  }
51
53
  }
52
- return fetch(changesApiPath, {
54
+ return fetch(getChangesApiPath(), {
53
55
  method: 'DELETE',
54
56
  body: JSON.stringify({
55
57
  fileName: key
@@ -66,7 +68,7 @@ sap.ui.define(["sap/base/util/merge", "sap/ui/fl/write/api/connectors/ObjectStor
66
68
  // not implemented
67
69
  },
68
70
  getItems: async function () {
69
- const response = await fetch(changesApiPath, {
71
+ const response = await fetch(getChangesApiPath(), {
70
72
  method: 'GET',
71
73
  headers: {
72
74
  'content-type': 'application/json'
@@ -6,8 +6,10 @@ import { CHANGES_API_PATH as CHANGES_API_PATH_STATIC, getFlexSettings } from './
6
6
  import { getUi5Version, isLowerThanMinimalUi5Version } from '../utils/version.js';
7
7
  import { getAdditionalChangeInfo } from '../utils/additional-change-info.js';
8
8
 
9
- const baseUrl = document.getElementById('sap-ui-bootstrap')?.dataset.openUxPreviewBaseUrl ?? '';
10
- const changesApiPath = `${baseUrl}${CHANGES_API_PATH_STATIC}`;
9
+ const getChangesApiPath = (): string => {
10
+ const baseUrl = document.getElementById('sap-ui-bootstrap')?.dataset.openUxPreviewBaseUrl ?? '';
11
+ return `${baseUrl}${CHANGES_API_PATH_STATIC}`;
12
+ };
11
13
 
12
14
  const connector = merge({}, ObjectStorageConnector, {
13
15
  layers: [Layer.VENDOR, Layer.CUSTOMER_BASE],
@@ -36,7 +38,7 @@ const connector = merge({}, ObjectStorageConnector, {
36
38
  additionalChangeInfo
37
39
  };
38
40
 
39
- return fetch(changesApiPath, {
41
+ return fetch(getChangesApiPath(), {
40
42
  method: 'POST',
41
43
  body: JSON.stringify(body, null, 2),
42
44
  headers: {
@@ -53,7 +55,7 @@ const connector = merge({}, ObjectStorageConnector, {
53
55
  }
54
56
  }
55
57
 
56
- return fetch(changesApiPath, {
58
+ return fetch(getChangesApiPath(), {
57
59
  method: 'DELETE',
58
60
  body: JSON.stringify({ fileName: key }),
59
61
  headers: {
@@ -68,7 +70,7 @@ const connector = merge({}, ObjectStorageConnector, {
68
70
  // not implemented
69
71
  },
70
72
  getItems: async function (): Promise<FlexChange[]> {
71
- const response = await fetch(changesApiPath, {
73
+ const response = await fetch(getChangesApiPath(), {
72
74
  method: 'GET',
73
75
  headers: {
74
76
  'content-type': 'application/json'
@@ -1,10 +1,157 @@
1
1
  "use strict";
2
2
 
3
- sap.ui.define([], function () {
3
+ sap.ui.define(["sap/base/Log", "open/ux/preview/client/thirdparty/@sap-ux-private/control-property-editor-common", "../utils/error", "../utils/version", "../utils/info-center-message"], function (Log, ___sap_ux_private_control_property_editor_common, ___utils_errorjs, ___utils_versionjs, ___utils_info_center_messagejs) {
4
4
  "use strict";
5
5
 
6
+ const MessageBarType = ___sap_ux_private_control_property_editor_common["MessageBarType"];
7
+ const getError = ___utils_errorjs["getError"];
8
+ const isLowerThanMinimalUi5Version = ___utils_versionjs["isLowerThanMinimalUi5Version"];
9
+ const sendInfoCenterMessage = ___utils_info_center_messagejs["sendInfoCenterMessage"];
10
+ const CONTROLLER_EXTENSION_PATH_REGEX = /\/changes\/coding\/.+\.(js|ts)/;
11
+
12
+ /**
13
+ * Extracts an Error object from a global error event.
14
+ * Handles both synchronous errors (ErrorEvent) and unhandled promise rejections (PromiseRejectionEvent).
15
+ *
16
+ * @param {GlobalErrorEvent} event - The global error or unhandled rejection event.
17
+ * @returns {Error | undefined} The extracted Error instance, or undefined if no Error could be extracted.
18
+ */
19
+ function extractError(event) {
20
+ if ('error' in event && event.error instanceof Error) {
21
+ return event.error;
22
+ }
23
+ if ('reason' in event && event.reason instanceof Error) {
24
+ return event.reason;
25
+ }
26
+ return undefined;
27
+ }
28
+
29
+ /**
30
+ * Reports controller extension errors to the Info Center.
31
+ * Filters events by checking if the stack trace contains 'ControllerExtension',
32
+ * and sends matching errors as error-level messages to the Info Center panel.
33
+ *
34
+ * @param {GlobalErrorEvent} event - The global error or unhandled rejection event.
35
+ */
36
+ const reportControllerExtensionErrorToInfoCenter = event => {
37
+ const error = extractError(event);
38
+ const stackTrace = error?.stack ?? '';
39
+ if (!CONTROLLER_EXTENSION_PATH_REGEX.test(stackTrace)) {
40
+ return;
41
+ }
42
+ void sendInfoCenterMessage({
43
+ title: {
44
+ key: 'CONTROLLER_EXTENSION_UNHANDLED_ERROR_TITLE'
45
+ },
46
+ description: error?.message ?? '',
47
+ type: MessageBarType.error,
48
+ details: stackTrace
49
+ });
50
+ };
51
+
52
+ /**
53
+ * Registers global event listeners for uncaught errors and unhandled promise rejections
54
+ * to detect and report controller extension errors to the Info Center.
55
+ */
56
+ function registerForControllerExtensionErrors() {
57
+ globalThis.addEventListener('error', reportControllerExtensionErrorToInfoCenter);
58
+ globalThis.addEventListener('unhandledrejection', reportControllerExtensionErrorToInfoCenter);
59
+ }
6
60
  const CHANGES_API_PATH = '/preview/api/changes';
7
61
 
62
+ /**
63
+ * SAPUI5 delivered namespaces from https://ui5.sap.com/#/api/sap
64
+ */
65
+ const UI5_LIBS = ['sap.apf', 'sap.base', 'sap.chart', 'sap.collaboration', 'sap.f', 'sap.fe', 'sap.fileviewer', 'sap.gantt', 'sap.landvisz', 'sap.m', 'sap.ndc', 'sap.ovp', 'sap.rules', 'sap.suite', 'sap.tnt', 'sap.ui', 'sap.uiext', 'sap.ushell', 'sap.uxap', 'sap.viz', 'sap.webanalytics', 'sap.zen'];
66
+ /**
67
+ * Check whether a specific dependency is a custom library, and if yes, add it to the map.
68
+ *
69
+ * @param dependency dependency from the manifest
70
+ * @param customLibs map containing the required custom libraries
71
+ */
72
+ function addKeys(dependency, customLibs) {
73
+ Object.keys(dependency).forEach(function (key) {
74
+ if (!UI5_LIBS.some(function (substring) {
75
+ return key === substring || key.startsWith(substring + '.');
76
+ })) {
77
+ customLibs[key] = true;
78
+ }
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Check whether a specific ComponentUsage is a custom component, and if yes, add it to the map.
84
+ *
85
+ * @param compUsages ComponentUsage from the manifest
86
+ * @param customLibs map containing the required custom libraries
87
+ */
88
+ function getComponentUsageNames(compUsages, customLibs) {
89
+ const compNames = Object.keys(compUsages).map(function (compUsageKey) {
90
+ return compUsages[compUsageKey].name;
91
+ });
92
+ compNames.forEach(function (key) {
93
+ if (!UI5_LIBS.some(function (substring) {
94
+ return key === substring || key.startsWith(substring + '.');
95
+ })) {
96
+ customLibs[key] = true;
97
+ }
98
+ });
99
+ }
100
+
101
+ /**
102
+ * Fetch the manifest for all the given application urls and generate a string containing all required custom library ids.
103
+ *
104
+ * @param appUrls urls pointing to included applications
105
+ * @returns Promise of a comma separated list of all required libraries.
106
+ */
107
+ async function getManifestLibs(appUrls) {
108
+ const result = {};
109
+ const promises = [];
110
+ for (const url of appUrls) {
111
+ promises.push(fetch(`${url}/manifest.json`).then(async resp => {
112
+ const manifest = await resp.json();
113
+ if (manifest) {
114
+ if (manifest['sap.ui5']?.dependencies) {
115
+ if (manifest['sap.ui5'].dependencies.libs) {
116
+ addKeys(manifest['sap.ui5'].dependencies.libs, result);
117
+ }
118
+ if (manifest['sap.ui5'].dependencies.components) {
119
+ addKeys(manifest['sap.ui5'].dependencies.components, result);
120
+ }
121
+ }
122
+ if (manifest['sap.ui5']?.componentUsages) {
123
+ getComponentUsageNames(manifest['sap.ui5'].componentUsages, result);
124
+ }
125
+ }
126
+ }));
127
+ }
128
+ return Promise.all(promises).then(() => Object.keys(result).join(','));
129
+ }
130
+
131
+ /**
132
+ * Register the custom libraries and their url with the UI5 loader.
133
+ *
134
+ * @param dataFromAppIndex data returned from the app index service
135
+ */
136
+ function registerModules(dataFromAppIndex) {
137
+ Object.keys(dataFromAppIndex).forEach(function (moduleDefinitionKey) {
138
+ const moduleDefinition = dataFromAppIndex[moduleDefinitionKey];
139
+ if (moduleDefinition?.dependencies) {
140
+ moduleDefinition.dependencies.forEach(function (dependency) {
141
+ if (dependency.url && dependency.url.length > 0 && dependency.type === 'UI5LIB') {
142
+ Log.info('Registering Library ' + dependency.componentId + ' from server ' + dependency.url);
143
+ const compId = dependency.componentId.replaceAll('.', '/');
144
+ const config = {
145
+ paths: {}
146
+ };
147
+ config.paths[compId] = dependency.url;
148
+ sap.ui.loader.config(config);
149
+ }
150
+ });
151
+ }
152
+ });
153
+ }
154
+
8
155
  /**
9
156
  * Retrieves Flex settings from a 'sap-ui-bootstrap' element's data attribute.
10
157
  * Parses the 'data-open-ux-preview-flex-settings' attribute as JSON.
@@ -20,11 +167,156 @@ sap.ui.define([], function () {
20
167
  }
21
168
  return result;
22
169
  }
170
+
171
+ /**
172
+ * Fetch the app state from the given application urls, then reset the app state.
173
+ *
174
+ * @param container the UShell container
175
+ */
176
+ async function resetAppState(container) {
177
+ const urlParams = new URLSearchParams(globalThis.location.hash);
178
+ const appStateValue = urlParams.get('sap-iapp-state') ?? urlParams.get('/?sap-iapp-state');
179
+ if (appStateValue) {
180
+ const appStateService = await container.getServiceAsync('AppState');
181
+ appStateService.deleteAppState(appStateValue);
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Fetch the manifest from the given application urls, then parse them for custom libs, and finally request their urls.
187
+ *
188
+ * @param appUrls application urls
189
+ * @param urlParams URLSearchParams object
190
+ * @returns returns a promise when the registration is completed.
191
+ */
192
+ async function registerComponentDependencyPaths(appUrls, urlParams) {
193
+ const libs = await getManifestLibs(appUrls);
194
+ if (libs && libs.length > 0) {
195
+ let url = '/sap/bc/ui2/app_index/ui5_app_info?id=' + encodeURIComponent(libs);
196
+ const sapClient = urlParams.get('sap-client');
197
+ if (sapClient?.length === 3 && /^\d+$/.test(sapClient)) {
198
+ url = url + '&sap-client=' + sapClient;
199
+ }
200
+ const response = await fetch(url);
201
+ try {
202
+ registerModules(await response.json());
203
+ } catch (error) {
204
+ Log.error(`Registering of reuse libs failed. Error:${error}`);
205
+ }
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Handle higher layer changes when starting UI Adaptation.
211
+ * When RTA detects higher layer changes an error with Reload triggered text is thrown, the RTA instance is destroyed and the application is reloaded.
212
+ * For UI5 version lower than 1.84.0 RTA is showing a popup with notification text about the detection of higher layer changes.
213
+ *
214
+ * @param error the error thrown when there are higher layer changes when starting UI Adaptation.
215
+ * @param ui5VersionInfo ui5 version info
216
+ */
217
+ async function handleHigherLayerChanges(error, ui5VersionInfo) {
218
+ const err = getError(error);
219
+ if (err.message.includes('Reload triggered')) {
220
+ if (!isLowerThanMinimalUi5Version(ui5VersionInfo, {
221
+ major: 1,
222
+ minor: 84
223
+ })) {
224
+ await sendInfoCenterMessage({
225
+ title: {
226
+ key: 'HIGHER_LAYER_CHANGES_TITLE'
227
+ },
228
+ description: {
229
+ key: 'HIGHER_LAYER_CHANGES_INFO_MESSAGE'
230
+ },
231
+ type: MessageBarType.warning
232
+ });
233
+ }
234
+ globalThis.location.reload();
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Starts UI adaptation (RTA) for the given component instance.
240
+ * Contains the inner logic shared between sandbox 1 (sandbox1Init.ts) and sandbox 2 (sandbox2AfterInit.ts).
241
+ *
242
+ * @param view - the component instance returned by AppLifeCycle attachAppLoaded
243
+ * @param flexSettings - the parsed flex settings
244
+ * @param ui5VersionInfo - current UI5 version
245
+ */
246
+ function startRtaForAppInstance(view, flexSettings, ui5VersionInfo) {
247
+ const {
248
+ pluginScript,
249
+ ...flexSettingsWithoutPlugin
250
+ } = flexSettings;
251
+ const libs = [];
252
+ if (isLowerThanMinimalUi5Version(ui5VersionInfo, {
253
+ major: 1,
254
+ minor: 72
255
+ })) {
256
+ libs.push('open/ux/preview/client/flp/initRta');
257
+ } else {
258
+ libs.push('sap/ui/rta/api/startAdaptation');
259
+ }
260
+ if (pluginScript) {
261
+ libs.push(pluginScript);
262
+ }
263
+ const options = {
264
+ rootControl: view,
265
+ validateAppVersion: false,
266
+ flexSettings: flexSettingsWithoutPlugin
267
+ };
268
+ sap.ui.require(libs, async function (startAdaptation, pluginScript) {
269
+ try {
270
+ await startAdaptation(options, pluginScript);
271
+ } catch (error) {
272
+ await sendInfoCenterMessage({
273
+ title: {
274
+ key: 'FLP_ADAPTATION_START_FAILED_TITLE'
275
+ },
276
+ description: getError(error).message,
277
+ type: MessageBarType.error
278
+ });
279
+ await handleHigherLayerChanges(error, ui5VersionInfo);
280
+ }
281
+ });
282
+ }
283
+
284
+ /**
285
+ * Dynamically adds a "Generate Card" action to the SAP Fiori Launchpad for the given component instance.
286
+ *
287
+ * @param componentInstance - The component instance for which the card generation action is added.
288
+ * @param container - The SAP Fiori Launchpad container used to access services.
289
+ */
290
+ function addCardGenerationUserAction(componentInstance, container) {
291
+ sap.ui.require(['sap/cards/ap/generator/CardGenerator'], async CardGenerator => {
292
+ const extensionService = await container.getServiceAsync('Extension');
293
+ const controlProperties = {
294
+ icon: 'sap-icon://add',
295
+ id: 'generate_card',
296
+ text: 'Generate Card',
297
+ tooltip: 'Generate Card',
298
+ press: () => {
299
+ CardGenerator.initializeAsync(componentInstance);
300
+ }
301
+ };
302
+ const parameters = {
303
+ controlType: 'sap.ushell.ui.launchpad.ActionItem'
304
+ };
305
+ const generateCardAction = await extensionService.createUserAction(controlProperties, parameters);
306
+ generateCardAction.showForCurrentApp();
307
+ });
308
+ }
23
309
  var __exports = {
24
310
  __esModule: true
25
311
  };
312
+ __exports.registerForControllerExtensionErrors = registerForControllerExtensionErrors;
26
313
  __exports.CHANGES_API_PATH = CHANGES_API_PATH;
27
314
  __exports.getFlexSettings = getFlexSettings;
315
+ __exports.resetAppState = resetAppState;
316
+ __exports.registerComponentDependencyPaths = registerComponentDependencyPaths;
317
+ __exports.handleHigherLayerChanges = handleHigherLayerChanges;
318
+ __exports.startRtaForAppInstance = startRtaForAppInstance;
319
+ __exports.addCardGenerationUserAction = addCardGenerationUserAction;
28
320
  return __exports;
29
321
  });
30
322
  //# sourceMappingURL=common.js.map