@salesforce-ui-embedding/internal-lightning-out-containers 2.2.7-rc.2
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/LICENSE.txt +21 -0
- package/dist/modules/force/lightningOutBaseContainer/lightningOutBaseContainer.html +12 -0
- package/dist/modules/force/lightningOutBaseContainer/lightningOutBaseContainer.js +286 -0
- package/dist/modules/force/lightningOutBaseContainer/lightningOutBaseContainer.js-meta.xml +8 -0
- package/dist/modules/force/lightningOutBaseContainer/utils/EmbeddingResizer.js +117 -0
- package/dist/modules/force/lightningOutBaseContainer/utils/__tests__/env.test.js +78 -0
- package/dist/modules/force/lightningOutBaseContainer/utils/constants.js +18 -0
- package/dist/modules/force/lightningOutBaseContainer/utils/env.js +74 -0
- package/dist/modules/force/lightningOutBaseContainer/utils/errorHandling.js +16 -0
- package/dist/modules/force/lightningOutBaseContainer/utils/gater.js +16 -0
- package/dist/modules/force/lightningOutBaseContainer/utils/instrumentation.js +71 -0
- package/dist/modules/force/lightningOutBaseContainer/utils/style.js +21 -0
- package/dist/modules/force/lightningOutLWRAuraContainer/lightningOutLWRAuraContainer.html +22 -0
- package/dist/modules/force/lightningOutLWRAuraContainer/lightningOutLWRAuraContainer.js +125 -0
- package/dist/modules/force/lightningOutLWRAuraContainer/lightningOutLWRAuraContainer.js-meta.xml +8 -0
- package/dist/modules/force/lightningOutLWRAuraContainer/utils/errorHandling.js +55 -0
- package/dist/modules/force/lightningOutLWRAuraContainer/utils/gater.js +14 -0
- package/dist/modules/force/lightningOutLWRContainer/lightningOutLWRContainer.html +12 -0
- package/dist/modules/force/lightningOutLWRContainer/lightningOutLWRContainer.js +7 -0
- package/dist/modules/force/lightningOutLWRContainer/lightningOutLWRContainer.js-meta.xml +12 -0
- package/dist/modules/force/lightningOutOneRuntimeContainer/lightningOutOneRuntimeContainer.html +19 -0
- package/dist/modules/force/lightningOutOneRuntimeContainer/lightningOutOneRuntimeContainer.js +57 -0
- package/dist/modules/force/lightningOutOneRuntimeContainer/lightningOutOneRuntimeContainer.js-meta.xml +9 -0
- package/dist/modules/force/lightningOutOneRuntimeContainer/utils/errorHandling.js +49 -0
- package/dist/modules/force/lightningOutOneRuntimeContainer/utils/gater.js +14 -0
- package/package.json +24 -0
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Terms of Use
|
|
2
|
+
|
|
3
|
+
Copyright 2026 Salesforce, Inc. All rights reserved.
|
|
4
|
+
|
|
5
|
+
These Terms of Use govern the download, installation, and/or use of this software provided by Salesforce, Inc. (“Salesforce”) (the “Software”), were last updated on April 15, 2022, ** and constitute a legally binding agreement between you and Salesforce. If you do not agree to these Terms of Use, do not install or use the Software.
|
|
6
|
+
|
|
7
|
+
Salesforce grants you a worldwide, non-exclusive, no-charge, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute the Software and derivative works subject to these Terms. These Terms shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
Subject to the limited rights expressly granted hereunder, Salesforce reserves all rights, title, and interest in and to all intellectual property subsisting in the Software. No rights are granted to you hereunder other than as expressly set forth herein. Users residing in countries on the United States Office of Foreign Assets Control sanction list, or which are otherwise subject to a US export embargo, may not use the Software.
|
|
10
|
+
|
|
11
|
+
Implementation of the Software may require development work, for which you are responsible. The Software may contain bugs, errors and incompatibilities and is made available on an AS IS basis without support, updates, or service level commitments.
|
|
12
|
+
|
|
13
|
+
Salesforce reserves the right at any time to modify, suspend, or discontinue, the Software (or any part thereof) with or without notice. You agree that Salesforce shall not be liable to you or to any third party for any modification, suspension, or discontinuance.
|
|
14
|
+
|
|
15
|
+
You agree to defend Salesforce against any claim, demand, suit or proceeding made or brought against Salesforce by a third party arising out of or accruing from (a) your use of the Software, and (b) any application you develop with the Software that infringes any copyright, trademark, trade secret, trade dress, patent, or other intellectual property right of any person or defames any person or violates their rights of publicity or privacy (each a “Claim Against Salesforce”), and will indemnify Salesforce from any damages, attorney fees, and costs finally awarded against Salesforce as a result of, or for any amounts paid by Salesforce under a settlement approved by you in writing of, a Claim Against Salesforce, provided Salesforce (x) promptly gives you written notice of the Claim Against Salesforce, (y) gives you sole control of the defense and settlement of the Claim Against Salesforce (except that you may not settle any Claim Against Salesforce unless it unconditionally releases Salesforce of all liability), and (z) gives you all reasonable assistance, at your expense.
|
|
16
|
+
|
|
17
|
+
WITHOUT LIMITING THE GENERALITY OF THE FOREGOING, THE SOFTWARE IS NOT SUPPORTED AND IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. IN NO EVENT SHALL SALESFORCE HAVE ANY LIABILITY FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, INDIRECT, SPECIAL, INCIDENTAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES, OR DAMAGES BASED ON LOST PROFITS, DATA, OR USE, IN CONNECTION WITH THE SOFTWARE, HOWEVER CAUSED AND WHETHER IN CONTRACT, TORT, OR UNDER ANY OTHER THEORY OF LIABILITY, WHETHER OR NOT YOU HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
|
18
|
+
|
|
19
|
+
These Terms of Use shall be governed exclusively by the internal laws of the State of California, without regard to its conflicts of laws rules. Each party hereby consents to the exclusive jurisdiction of the state and federal courts located in San Francisco County, California to adjudicate any dispute arising out of or relating to these Terms of Use and the download, installation, and/or use of the Software. Except as expressly stated herein, these Terms of Use constitute the entire agreement between the parties, and supersede all prior and contemporaneous agreements, proposals, or representations, written or oral, concerning their subject matter. No modification, amendment, or waiver of any provision of these Terms of Use shall be effective unless it is by an update to these Terms of Use that Salesforce makes available, or is in writing and signed by the party against whom the modification, amendment, or waiver is to be asserted.
|
|
20
|
+
|
|
21
|
+
Data Privacy: Salesforce may collect, process, and store device, system, and other information related to your use of the Software. This information includes, but is not limited to, IP address, user metrics, and other data (“Usage Data”). Salesforce may use Usage Data for analytics, product development, and marketing purposes. You acknowledge that files generated in conjunction with the Software may contain sensitive or confidential data, and you are solely responsible for anonymizing and protecting such data.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Copyright 2026 Salesforce, Inc. All rights reserved.
|
|
3
|
+
For full license text, see the LICENSE.txt file in this package.
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<template lwc:render-mode="light">
|
|
7
|
+
<lwc:component
|
|
8
|
+
lwc:is={embeddedCmpCtor}
|
|
9
|
+
lwc:ref="embeddedCmp"
|
|
10
|
+
lwc:spread={embeddedCmpProps}
|
|
11
|
+
></lwc:component>
|
|
12
|
+
</template>
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 salesforce.com, inc.
|
|
3
|
+
* All Rights Reserved
|
|
4
|
+
* Company Confidential
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { api, LightningElement, track } from "lwc";
|
|
8
|
+
import { pdpEventSchema } from "o11y_schema/sf_pdp";
|
|
9
|
+
|
|
10
|
+
import { MSG } from "./utils/constants";
|
|
11
|
+
import { EmbeddingResizer } from "./utils/EmbeddingResizer";
|
|
12
|
+
import { createEnvContext } from "./utils/env";
|
|
13
|
+
import { createGlobalError } from "./utils/errorHandling";
|
|
14
|
+
import { createInstrumentation } from "./utils/instrumentation";
|
|
15
|
+
import { applyCustomStyleProperties } from "./utils/style";
|
|
16
|
+
|
|
17
|
+
const ENABLE_RESIZING = false;
|
|
18
|
+
|
|
19
|
+
// These settings are important for layout and sizing.
|
|
20
|
+
document.documentElement.style.display = "block";
|
|
21
|
+
document.body.style.margin = "0px";
|
|
22
|
+
document.body.style.display = "block";
|
|
23
|
+
if (!ENABLE_RESIZING) {
|
|
24
|
+
document.documentElement.style.height = "100%";
|
|
25
|
+
document.body.style.height = "100%";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Apply global style overrides from URL parameter during bootstrap
|
|
29
|
+
const urlSearchParams = new URLSearchParams(window.location.search);
|
|
30
|
+
const globalStyleParam = urlSearchParams.get("globalStyle");
|
|
31
|
+
if (globalStyleParam) {
|
|
32
|
+
try {
|
|
33
|
+
// Apply CSS custom properties directly to document.documentElement
|
|
34
|
+
applyCustomStyleProperties(globalStyleParam, document.documentElement.style);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
window.console.error("Failed to apply globalStyle parameter:", err);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default class LightningOutBaseContainer extends LightningElement {
|
|
41
|
+
static renderMode = "light";
|
|
42
|
+
|
|
43
|
+
#embeddedCmp;
|
|
44
|
+
embeddedCmpCtor;
|
|
45
|
+
embeddedCmpName;
|
|
46
|
+
@track embeddedCmpProps;
|
|
47
|
+
embeddedCmpType = "lwc";
|
|
48
|
+
|
|
49
|
+
#env;
|
|
50
|
+
#eventListeners = new Map();
|
|
51
|
+
#renderActivity;
|
|
52
|
+
#renderActivityStopped = false;
|
|
53
|
+
|
|
54
|
+
#globalError;
|
|
55
|
+
isError = false;
|
|
56
|
+
#instrumentation;
|
|
57
|
+
|
|
58
|
+
// Resize observer
|
|
59
|
+
#resizer = null;
|
|
60
|
+
|
|
61
|
+
constructor() {
|
|
62
|
+
super();
|
|
63
|
+
this.#env = createEnvContext();
|
|
64
|
+
this.#instrumentation = createInstrumentation(
|
|
65
|
+
"LightningOutLWRApp",
|
|
66
|
+
this.#env.componentNameParam,
|
|
67
|
+
this.#env.loAppOrigin,
|
|
68
|
+
);
|
|
69
|
+
this.#globalError = createGlobalError(this.#env.parentPostMessage, this.#instrumentation.logError);
|
|
70
|
+
window.addEventListener("message", this.#messageListener);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@api
|
|
74
|
+
get embeddedCmp() {
|
|
75
|
+
return this.refs?.embeddedCmp;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getEnv() {
|
|
79
|
+
return this.#env;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getInstrumentation() {
|
|
83
|
+
return this.#instrumentation;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#messageListener = (event) => {
|
|
87
|
+
// Page must be in an <iframe> unless testMode
|
|
88
|
+
if (!this.#env.testMode && window.top === window.self) {
|
|
89
|
+
window.console.trace("Lightning Out: This page is not in an <iframe>");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (event.origin !== this.#env.loAppOrigin) {
|
|
93
|
+
window.console.log(
|
|
94
|
+
`Lightning Out: Message ignored due to different origin: ${event.origin} vs ${this.#env.loAppOrigin}`,
|
|
95
|
+
);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
switch (event.data.type) {
|
|
99
|
+
case MSG.ADD_EVENT_LISTENER: {
|
|
100
|
+
const eventName = event.data.name;
|
|
101
|
+
const eventOptions = event.data.options;
|
|
102
|
+
const listenerKey = event.data.listenerKey;
|
|
103
|
+
const listener = (evt) => {
|
|
104
|
+
this.#env.parentPostMessage({
|
|
105
|
+
name: eventName,
|
|
106
|
+
detail: structuredClone(evt.detail),
|
|
107
|
+
type: MSG.DISPATCH_EVENT,
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
this.#embeddedCmp.addEventListener(eventName, listener, eventOptions);
|
|
111
|
+
this.#eventListeners.set(listenerKey, listener);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case MSG.DISPATCH_EVENT: {
|
|
115
|
+
const customEvent = new CustomEvent(event.data.name, {
|
|
116
|
+
detail: event.data.detail,
|
|
117
|
+
});
|
|
118
|
+
this.#embeddedCmp.dispatchEvent(customEvent);
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
case MSG.REMOVE_EVENT_LISTENER: {
|
|
122
|
+
const eventName = event.data.name;
|
|
123
|
+
const eventOptions = event.data.options;
|
|
124
|
+
const listenerKey = event.data.listenerKey;
|
|
125
|
+
const listener = this.#eventListeners.get(listenerKey);
|
|
126
|
+
if (listener) {
|
|
127
|
+
this.#embeddedCmp.removeEventListener(eventName, listener, eventOptions);
|
|
128
|
+
this.#eventListeners.delete(listenerKey);
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case MSG.SET_COMPONENT_DATA: {
|
|
133
|
+
this.renderComponent(event.data.componentData);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
case MSG.SET_COMPONENT_PROPS: {
|
|
137
|
+
this.setComponentProps(event.data.componentProps);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case MSG.RELOAD: {
|
|
141
|
+
window.location.reload();
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
default: {
|
|
145
|
+
// noop
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
async renderComponent(componentData) {
|
|
151
|
+
this.embeddedCmpId = componentData.id;
|
|
152
|
+
this.embeddedCmpName = componentData.name;
|
|
153
|
+
this.embeddedCmpProps = componentData.props;
|
|
154
|
+
if (!this.embeddedCmpName || !this.embeddedCmpProps) {
|
|
155
|
+
this.#globalError(`Missing 'componentName' or 'componentProps,' both must be specified.`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const { specifier, type } = this.resolveEmbeddedCmpSpecifier(this.embeddedCmpName);
|
|
159
|
+
this.embeddedCmpType = type;
|
|
160
|
+
|
|
161
|
+
this.#renderActivity = this.#instrumentation.startRenderActivity({
|
|
162
|
+
componentName: specifier,
|
|
163
|
+
componentType: type,
|
|
164
|
+
loAppOrigin: this.#env.loAppOrigin,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
// eslint-disable-next-line @lwc/lwc-platform/no-dynamic-import-identifier
|
|
169
|
+
const { default: ctor } = await import(specifier);
|
|
170
|
+
this.embeddedCmpCtor = ctor;
|
|
171
|
+
} catch (err) {
|
|
172
|
+
this.isError = true;
|
|
173
|
+
this.#instrumentation.errorRenderActivity(this.#renderActivity, err, {
|
|
174
|
+
componentName: specifier,
|
|
175
|
+
componentType: type,
|
|
176
|
+
componentProps: JSON.stringify(this.embeddedCmpProps || {}),
|
|
177
|
+
message: err?.message,
|
|
178
|
+
hasError: this.isError,
|
|
179
|
+
loAppOrigin: this.#env.loAppOrigin,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Direct-to-Argus: count render errors.
|
|
183
|
+
this.#instrumentation.incrementCounter("lo2.render.error.count", 1, true, {
|
|
184
|
+
componentType: type,
|
|
185
|
+
componentName: specifier,
|
|
186
|
+
});
|
|
187
|
+
this.#globalError(err);
|
|
188
|
+
} finally {
|
|
189
|
+
if (this.isError && this.#renderActivity && !this.#renderActivityStopped) {
|
|
190
|
+
this.#instrumentation.stopRenderActivity(this.#renderActivity, {
|
|
191
|
+
componentName: specifier,
|
|
192
|
+
componentType: type,
|
|
193
|
+
hasError: this.isError,
|
|
194
|
+
loAppOrigin: this.#env.loAppOrigin,
|
|
195
|
+
});
|
|
196
|
+
this.#renderActivityStopped = true;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
setComponentProps(componentProps) {
|
|
202
|
+
if (componentProps.style && this.embeddedCmp) {
|
|
203
|
+
applyCustomStyleProperties(componentProps.style, this.embeddedCmp.style);
|
|
204
|
+
delete componentProps.style;
|
|
205
|
+
}
|
|
206
|
+
if (Object.keys(componentProps).length) {
|
|
207
|
+
this.embeddedCmpProps = componentProps;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
resolveEmbeddedCmpSpecifier(embeddedCmpName) {
|
|
212
|
+
const [name] = embeddedCmpName.split("/v/");
|
|
213
|
+
const type = name.includes(":") ? "aura" : "lwc";
|
|
214
|
+
if (type === "aura") {
|
|
215
|
+
this.#globalError("Aura components require using 'lightningOutOneRuntimeContainer' instead.");
|
|
216
|
+
}
|
|
217
|
+
return { specifier: name, type };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
connectedCallback() {
|
|
221
|
+
this.#env.parentPostMessage({ type: MSG.LOADED });
|
|
222
|
+
const componentData = this.#env.testMode ? this.#env.getComponentData() : null;
|
|
223
|
+
if (componentData) {
|
|
224
|
+
this.renderComponent(componentData);
|
|
225
|
+
} else {
|
|
226
|
+
this.#env.parentPostMessage({ type: MSG.GET_COMPONENT_DATA });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
disconnectedCallback() {
|
|
231
|
+
if (ENABLE_RESIZING) {
|
|
232
|
+
this.#resizer?.stop();
|
|
233
|
+
this.#resizer = null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
#setupResizeObserver = () => {
|
|
238
|
+
this.#resizer = new EmbeddingResizer({
|
|
239
|
+
targetElement: document.body,
|
|
240
|
+
onResize: (height) => {
|
|
241
|
+
this.#env.parentPostMessage({
|
|
242
|
+
type: MSG.RESIZE,
|
|
243
|
+
height,
|
|
244
|
+
});
|
|
245
|
+
},
|
|
246
|
+
waitForDOMReady: false, // Already connected, no need to wait
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
this.#resizer.start();
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
renderedCallback() {
|
|
253
|
+
if (!this.#embeddedCmp && this.refs?.embeddedCmp) {
|
|
254
|
+
this.#embeddedCmp = this.refs.embeddedCmp;
|
|
255
|
+
this.#env.parentPostMessage({ type: MSG.READY });
|
|
256
|
+
if (ENABLE_RESIZING) {
|
|
257
|
+
this.#setupResizeObserver();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// PDP_ProductUsage telemetry
|
|
261
|
+
if (this.#instrumentation?.log) {
|
|
262
|
+
this.#instrumentation.log(pdpEventSchema, {
|
|
263
|
+
eventName: "lightningOutContainer.ready",
|
|
264
|
+
productFeatureId: "aJCEE000000152T4AQ",
|
|
265
|
+
contextName: "componentType",
|
|
266
|
+
contextValue: this.embeddedCmpType,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (this.#renderActivity && !this.#renderActivityStopped) {
|
|
271
|
+
this.#instrumentation.stopRenderActivity(this.#renderActivity, {
|
|
272
|
+
componentName: this.embeddedCmpName,
|
|
273
|
+
componentType: this.embeddedCmpType,
|
|
274
|
+
hasError: this.isError,
|
|
275
|
+
loAppOrigin: this.#env.loAppOrigin,
|
|
276
|
+
});
|
|
277
|
+
this.#renderActivityStopped = true;
|
|
278
|
+
|
|
279
|
+
// Direct-to-Argus: count successful renders.
|
|
280
|
+
this.#instrumentation.incrementCounter("lo2.render.count", 1, false, {
|
|
281
|
+
componentType: this.embeddedCmpType,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!--
|
|
3
|
+
Copyright 2026 Salesforce, Inc. All rights reserved.
|
|
4
|
+
For full license text, see the LICENSE.txt file in this package.
|
|
5
|
+
-->
|
|
6
|
+
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
7
|
+
<isExposed>false</isExposed>
|
|
8
|
+
</LightningComponentBundle>
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EmbeddingResizer - Handles dynamic iframe/container resizing
|
|
3
|
+
* Uses ResizeObserver to monitor element size changes and notify the host
|
|
4
|
+
*/
|
|
5
|
+
class EmbeddingResizer {
|
|
6
|
+
#observer = null;
|
|
7
|
+
#targetElement;
|
|
8
|
+
#onResize;
|
|
9
|
+
#onReady;
|
|
10
|
+
#waitForDOMReady;
|
|
11
|
+
#lastHeight = -1;
|
|
12
|
+
#scheduled = false;
|
|
13
|
+
#isObserving = false;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.#targetElement = options.targetElement || document.body;
|
|
16
|
+
this.#onResize = options.onResize;
|
|
17
|
+
this.#onReady = options.onReady;
|
|
18
|
+
this.#waitForDOMReady = options.waitForDOMReady ?? true;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Start observing the target element for size changes
|
|
22
|
+
*/
|
|
23
|
+
start() {
|
|
24
|
+
if (this.#isObserving) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (this.#waitForDOMReady && document.readyState === "loading") {
|
|
28
|
+
document.addEventListener("DOMContentLoaded", () => this.#startObserver(), { once: true });
|
|
29
|
+
} else {
|
|
30
|
+
this.#startObserver();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Stop observing and clean up resources
|
|
35
|
+
*/
|
|
36
|
+
stop() {
|
|
37
|
+
if (this.#observer) {
|
|
38
|
+
this.#observer.disconnect();
|
|
39
|
+
this.#observer = null;
|
|
40
|
+
}
|
|
41
|
+
this.#isObserving = false;
|
|
42
|
+
this.#lastHeight = -1;
|
|
43
|
+
this.#scheduled = false;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get the current observed height
|
|
47
|
+
*/
|
|
48
|
+
getLastHeight() {
|
|
49
|
+
return this.#lastHeight;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Check if currently observing
|
|
53
|
+
*/
|
|
54
|
+
isObserving() {
|
|
55
|
+
return this.#isObserving;
|
|
56
|
+
}
|
|
57
|
+
#startObserver = () => {
|
|
58
|
+
this.#observer = new ResizeObserver((entries) => {
|
|
59
|
+
this.#handleResize(entries);
|
|
60
|
+
});
|
|
61
|
+
this.#observer.observe(this.#targetElement, { box: "border-box" });
|
|
62
|
+
this.#isObserving = true;
|
|
63
|
+
// Invoke ready callback if provided
|
|
64
|
+
if (this.#onReady) {
|
|
65
|
+
this.#onReady();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
#handleResize = (entries) => {
|
|
69
|
+
if (!entries || entries.length === 0) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const entry = entries[0];
|
|
73
|
+
const measuredHeight = this.#extractHeight(entry);
|
|
74
|
+
const heightPx = Math.max(0, Math.ceil(Number(measuredHeight)));
|
|
75
|
+
if (!Number.isFinite(heightPx)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (heightPx === this.#lastHeight) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
this.#lastHeight = heightPx;
|
|
82
|
+
if (!this.#scheduled) {
|
|
83
|
+
this.#scheduled = true;
|
|
84
|
+
// eslint-disable-next-line @lwc/lwc/no-async-operation
|
|
85
|
+
requestAnimationFrame(() => {
|
|
86
|
+
this.#scheduled = false;
|
|
87
|
+
if (!this.#isObserving) return;
|
|
88
|
+
try {
|
|
89
|
+
this.#onResize(this.#lastHeight);
|
|
90
|
+
} catch {
|
|
91
|
+
// Swallow: a throwing consumer (e.g. disposed bridge) must not wedge the rAF loop.
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
#extractHeight = (entry) => {
|
|
97
|
+
// Try borderBoxSize first (more accurate)
|
|
98
|
+
if (entry.borderBoxSize) {
|
|
99
|
+
if (Array.isArray(entry.borderBoxSize)) {
|
|
100
|
+
return entry.borderBoxSize[0]?.blockSize || 0;
|
|
101
|
+
}
|
|
102
|
+
// Some browsers return a single object instead of array
|
|
103
|
+
return entry.borderBoxSize.blockSize || 0;
|
|
104
|
+
}
|
|
105
|
+
// Fallback to contentRect
|
|
106
|
+
if (entry.contentRect && typeof entry.contentRect.height === "number") {
|
|
107
|
+
return entry.contentRect.height;
|
|
108
|
+
}
|
|
109
|
+
// Last resort: getBoundingClientRect
|
|
110
|
+
if (entry.target && typeof entry.target.getBoundingClientRect === "function") {
|
|
111
|
+
return entry.target.getBoundingClientRect().height;
|
|
112
|
+
}
|
|
113
|
+
return 0;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export { EmbeddingResizer };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 salesforce.com, inc.
|
|
3
|
+
* All Rights Reserved
|
|
4
|
+
* Company Confidential
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { assertBareSpecifier } from "../env";
|
|
8
|
+
import { isComponentNameCheckDisabled } from "../gater";
|
|
9
|
+
|
|
10
|
+
jest.mock("../gater", () => ({
|
|
11
|
+
isComponentNameCheckDisabled: jest.fn(() => false),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const mockIsDisabled = isComponentNameCheckDisabled;
|
|
15
|
+
|
|
16
|
+
describe("assertBareSpecifier", () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
mockIsDisabled.mockReturnValue(false);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe("accepts bare identifiers", () => {
|
|
22
|
+
it.each([
|
|
23
|
+
"c/foo",
|
|
24
|
+
"c/dealerLocator",
|
|
25
|
+
"namespace/component",
|
|
26
|
+
"c/foo/v/1_0_0",
|
|
27
|
+
"aura/interop/v/0_0_1",
|
|
28
|
+
"ns/name/v/2_3_4",
|
|
29
|
+
"c:foo",
|
|
30
|
+
"namespace:component",
|
|
31
|
+
])("accepts %s", (value) => {
|
|
32
|
+
expect(assertBareSpecifier(value)).toBe(value);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("rejects URL-shaped and unsafe strings", () => {
|
|
37
|
+
it.each([
|
|
38
|
+
"//www.arcgis.com/sharing/rest?f=json&callback=x",
|
|
39
|
+
"https://evil.com/payload.js",
|
|
40
|
+
"http://evil.com/payload.js",
|
|
41
|
+
"/absolute/path",
|
|
42
|
+
"../traversal/here",
|
|
43
|
+
"c/foo?query=1",
|
|
44
|
+
"c/foo#hash",
|
|
45
|
+
"c/foo/v/1_0_0?x=1",
|
|
46
|
+
"c/foo bar",
|
|
47
|
+
" c/foo",
|
|
48
|
+
"c/foo ",
|
|
49
|
+
"",
|
|
50
|
+
"c",
|
|
51
|
+
"/c/foo",
|
|
52
|
+
"c//foo",
|
|
53
|
+
"-invalid/foo",
|
|
54
|
+
"c/-invalid",
|
|
55
|
+
"c/foo/v/",
|
|
56
|
+
"c/foo/v/1.0.0",
|
|
57
|
+
])("rejects %s", (value) => {
|
|
58
|
+
expect(() => assertBareSpecifier(value)).toThrow(/Invalid componentName/);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("returns null for null input (missing URL param)", () => {
|
|
63
|
+
expect(assertBareSpecifier(null)).toBeNull();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("kill-switch gate", () => {
|
|
67
|
+
it("bypasses validation and returns value unchanged when gate is open", () => {
|
|
68
|
+
mockIsDisabled.mockReturnValue(true);
|
|
69
|
+
const unsafe = "https://evil.com/payload.js";
|
|
70
|
+
expect(assertBareSpecifier(unsafe)).toBe(unsafe);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("still returns null for null input when gate is open", () => {
|
|
74
|
+
mockIsDisabled.mockReturnValue(true);
|
|
75
|
+
expect(assertBareSpecifier(null)).toBeNull();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 salesforce.com, inc.
|
|
3
|
+
* All Rights Reserved
|
|
4
|
+
* Company Confidential
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const MSG = {
|
|
8
|
+
LOADED: "lo.loaded",
|
|
9
|
+
READY: "lo.ready",
|
|
10
|
+
ADD_EVENT_LISTENER: "lo.addEventListener",
|
|
11
|
+
REMOVE_EVENT_LISTENER: "lo.removeEventListener",
|
|
12
|
+
DISPATCH_EVENT: "lo.dispatchEvent",
|
|
13
|
+
SET_COMPONENT_DATA: "lo.setComponentData",
|
|
14
|
+
SET_COMPONENT_PROPS: "lo.setComponentProps",
|
|
15
|
+
RELOAD: "lo.reload",
|
|
16
|
+
GET_COMPONENT_DATA: "lo.getComponentData",
|
|
17
|
+
RESIZE: "lo.resize",
|
|
18
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 salesforce.com, inc.
|
|
3
|
+
* All Rights Reserved
|
|
4
|
+
* Company Confidential
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { isComponentNameCheckDisabled } from "./gater";
|
|
8
|
+
|
|
9
|
+
// Bare LWC (namespace/name) or Aura (namespace:name) identifier, with optional /v/<version> suffix.
|
|
10
|
+
// Rejects URL-shaped strings — a specifier reaching `import()` must be resolved as a module,
|
|
11
|
+
// not a URL. LWR's AMD loader treats URL-shaped specifiers as fetchable script sources, which
|
|
12
|
+
// would allow an attacker who controls the `componentName` URL param to load arbitrary script
|
|
13
|
+
// from any origin permitted by the page's CSP.
|
|
14
|
+
const BARE_SPECIFIER = /^[a-zA-Z][a-zA-Z0-9_-]*[/:][a-zA-Z][a-zA-Z0-9_-]*(\/v\/[a-zA-Z0-9_]+)?$/;
|
|
15
|
+
|
|
16
|
+
export function assertBareSpecifier(value) {
|
|
17
|
+
if (value === null) return null;
|
|
18
|
+
if (isComponentNameCheckDisabled()) return value;
|
|
19
|
+
if (!BARE_SPECIFIER.test(value)) {
|
|
20
|
+
throw new Error(`Invalid componentName: ${value}`);
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const urlSearchParams = new URLSearchParams(window.location.search);
|
|
26
|
+
|
|
27
|
+
export function createEnvContext() {
|
|
28
|
+
const loAppOrigin = urlSearchParams.get("loAppOrigin");
|
|
29
|
+
const parentElementId = urlSearchParams.get("parentElementId");
|
|
30
|
+
const componentNameParam = assertBareSpecifier(urlSearchParams.get("componentName"));
|
|
31
|
+
const testMode = urlSearchParams.get("testMode") === "true";
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const isInBuilder = window.self !== window.top && window.name === "sitePreviewFrame";
|
|
35
|
+
const isInBuilderContext =
|
|
36
|
+
urlSearchParams.get("app") === "commeditor" && urlSearchParams.get("view") === "editor";
|
|
37
|
+
if (!isInBuilder && !isInBuilderContext) {
|
|
38
|
+
(() => new URL(loAppOrigin))();
|
|
39
|
+
}
|
|
40
|
+
} catch (err) {
|
|
41
|
+
throw new Error(`${err.message} (loAppOrigin: ${loAppOrigin})`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parentPostMessage(message) {
|
|
45
|
+
if (typeof message !== "string") {
|
|
46
|
+
message.id = parentElementId;
|
|
47
|
+
}
|
|
48
|
+
window.parent.postMessage(message, loAppOrigin);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getComponentData() {
|
|
52
|
+
const componentProps = urlSearchParams.get("componentProps");
|
|
53
|
+
if (!componentProps) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
return {
|
|
58
|
+
name: componentNameParam,
|
|
59
|
+
props: JSON.parse(componentProps),
|
|
60
|
+
};
|
|
61
|
+
} catch {
|
|
62
|
+
throw new Error(`Invalid component properties: ${componentProps}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
loAppOrigin,
|
|
68
|
+
parentElementId,
|
|
69
|
+
componentNameParam,
|
|
70
|
+
testMode,
|
|
71
|
+
parentPostMessage,
|
|
72
|
+
getComponentData,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 salesforce.com, inc.
|
|
3
|
+
* All Rights Reserved
|
|
4
|
+
* Company Confidential
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function createGlobalError(parentPostMessage, logError) {
|
|
8
|
+
return function globalError(err) {
|
|
9
|
+
const msg = err?.message || `${err}`;
|
|
10
|
+
const fullMsg = `Lightning-Out Container Error: ${msg}`;
|
|
11
|
+
logError(fullMsg);
|
|
12
|
+
window.console.error(fullMsg);
|
|
13
|
+
parentPostMessage(fullMsg);
|
|
14
|
+
throw new Error(fullMsg);
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 salesforce.com, inc.
|
|
3
|
+
* All Rights Reserved
|
|
4
|
+
* Company Confidential
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import disableComponentNameCheckGate from "@salesforce/gate/org.salesforce.lo2.lwr.disableComponentNameCheck";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Kill-switch for componentName bare-specifier validation (W-23730644).
|
|
11
|
+
* SECURITY: opening re-enables arbitrary-script-load via URL-shaped componentName;
|
|
12
|
+
* bounded only by CSP. See env.ts BARE_SPECIFIER for the regex + threat model.
|
|
13
|
+
*/
|
|
14
|
+
export function isComponentNameCheckDisabled() {
|
|
15
|
+
return disableComponentNameCheckGate.isOpen({ fallback: false });
|
|
16
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 salesforce.com, inc.
|
|
3
|
+
* All Rights Reserved
|
|
4
|
+
* Company Confidential
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getInstrumentation } from "o11y/client";
|
|
8
|
+
import { lightningOutComponentRenderSchema } from "o11y_schema/sf_lightningOut";
|
|
9
|
+
|
|
10
|
+
export function createInstrumentation(channelName, componentName, loAppOrigin) {
|
|
11
|
+
const instrumentation = getInstrumentation(channelName);
|
|
12
|
+
|
|
13
|
+
function logError(error) {
|
|
14
|
+
if (instrumentation) {
|
|
15
|
+
instrumentation.error(error, lightningOutComponentRenderSchema, {
|
|
16
|
+
componentName,
|
|
17
|
+
message: error.message || String(error),
|
|
18
|
+
hasError: true,
|
|
19
|
+
loAppOrigin,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function log(schema, payload) {
|
|
25
|
+
if (instrumentation && typeof instrumentation.log === "function") {
|
|
26
|
+
instrumentation.log(schema, payload);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function startRenderActivity(instrumentationContext) {
|
|
31
|
+
if (instrumentation && typeof instrumentation.startActivity === "function") {
|
|
32
|
+
return instrumentation.startActivity("component-render", {
|
|
33
|
+
instrumentationContext,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function errorRenderActivity(activity, err, details) {
|
|
40
|
+
if (activity) {
|
|
41
|
+
activity.error(err, lightningOutComponentRenderSchema, details);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function stopRenderActivity(activity, details) {
|
|
46
|
+
if (activity) {
|
|
47
|
+
activity.stop(lightningOutComponentRenderSchema, details);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Direct-to-Argus counter emission. In production the o11y client's
|
|
52
|
+
// incrementCounter() flows through Core → Funnel → Argus.
|
|
53
|
+
// Metric naming: core_ui_LightningOutLWRApp:<scope>.<name>.COUNT.Count
|
|
54
|
+
function incrementCounter(name, value, isError, tags) {
|
|
55
|
+
if (!instrumentation || typeof instrumentation.incrementCounter !== "function") return;
|
|
56
|
+
try {
|
|
57
|
+
instrumentation.incrementCounter(name, value, isError, tags || {});
|
|
58
|
+
} catch {
|
|
59
|
+
// swallow — telemetry must never break the container
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
logError,
|
|
65
|
+
startRenderActivity,
|
|
66
|
+
errorRenderActivity,
|
|
67
|
+
stopRenderActivity,
|
|
68
|
+
log,
|
|
69
|
+
incrementCounter,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 salesforce.com, inc.
|
|
3
|
+
* All Rights Reserved
|
|
4
|
+
* Company Confidential
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function applyCustomStyleProperties(componentPropsStyle, embeddedCmpStyle) {
|
|
8
|
+
for (let i = 0, len = embeddedCmpStyle.length; i < len; i++) {
|
|
9
|
+
const prop = embeddedCmpStyle.item(i);
|
|
10
|
+
if (prop && prop.startsWith("--")) {
|
|
11
|
+
embeddedCmpStyle.removeProperty(prop);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const allStyles = componentPropsStyle.split(";");
|
|
15
|
+
allStyles.forEach((style) => {
|
|
16
|
+
const [prop, val] = style.split(":");
|
|
17
|
+
if (prop && prop.trim().startsWith("--")) {
|
|
18
|
+
embeddedCmpStyle.setProperty(prop.trim(), val ?? "");
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Copyright 2026 Salesforce, Inc. All rights reserved.
|
|
3
|
+
For full license text, see the LICENSE.txt file in this package.
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<template lwc:render-mode="light">
|
|
7
|
+
<div lwc:if={includeBeacon}>
|
|
8
|
+
<aura-interop
|
|
9
|
+
descriptor="instrumentation:beacon"
|
|
10
|
+
config={config}
|
|
11
|
+
></aura-interop>
|
|
12
|
+
</div>
|
|
13
|
+
<lwrlex-instrumentation></lwrlex-instrumentation>
|
|
14
|
+
|
|
15
|
+
<div lwc:if={componentName}>
|
|
16
|
+
<aura-interop
|
|
17
|
+
descriptor={componentName}
|
|
18
|
+
lwc:ref="embeddedCmp"
|
|
19
|
+
lwc:spread={embeddedCmpProps}>
|
|
20
|
+
</aura-interop>
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 salesforce.com, inc.
|
|
3
|
+
* All Rights Reserved
|
|
4
|
+
* Company Confidential
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import LightningOutBaseContainer from "force/lightningOutBaseContainer";
|
|
8
|
+
import { pdpEventSchema } from "o11y_schema/sf_pdp";
|
|
9
|
+
|
|
10
|
+
import { createErrorHooks } from "./utils/errorHandling";
|
|
11
|
+
import { isBeaconEnabled } from "./utils/gater";
|
|
12
|
+
|
|
13
|
+
export default class LightningOutLWRAuraContainer extends LightningOutBaseContainer {
|
|
14
|
+
#includeBeacon = false;
|
|
15
|
+
componentName = null;
|
|
16
|
+
config = {
|
|
17
|
+
usePageTransactions: false,
|
|
18
|
+
disableAvroValidation: false,
|
|
19
|
+
};
|
|
20
|
+
#renderActivity;
|
|
21
|
+
#renderActivityStopped = false;
|
|
22
|
+
#telemetryLogged = false;
|
|
23
|
+
|
|
24
|
+
constructor() {
|
|
25
|
+
super();
|
|
26
|
+
this.#includeBeacon = isBeaconEnabled();
|
|
27
|
+
createErrorHooks(this.getEnv().parentPostMessage, this.getInstrumentation().logError);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get includeBeacon() {
|
|
31
|
+
return this.#includeBeacon;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async renderComponent(componentData) {
|
|
35
|
+
if (!componentData.name) {
|
|
36
|
+
const error = new Error("LightningOutLWRAuraContainer requires 'componentName'.");
|
|
37
|
+
this.getInstrumentation().logError(error);
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const isAura = componentData.name.includes(":");
|
|
42
|
+
this.embeddedCmpType = isAura ? "aura" : "lwc";
|
|
43
|
+
this.embeddedCmpId = componentData.id || "componentId";
|
|
44
|
+
this.embeddedCmpName = componentData.name;
|
|
45
|
+
this.embeddedCmpProps = componentData.props || {};
|
|
46
|
+
this.componentName = componentData.name;
|
|
47
|
+
this.isError = false;
|
|
48
|
+
|
|
49
|
+
this.#renderActivityStopped = false;
|
|
50
|
+
this.#renderActivity = this.getInstrumentation().startRenderActivity(this._getInstrumentationContext());
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
this.getEnv().parentPostMessage({ type: "lo.ready" });
|
|
54
|
+
} catch (err) {
|
|
55
|
+
this.isError = true;
|
|
56
|
+
this._handleRenderError(err);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
setComponentProps(componentProps) {
|
|
61
|
+
if (!componentProps) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.embeddedCmpProps = componentProps;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
resolveEmbeddedCmpSpecifier(embeddedCmpName) {
|
|
68
|
+
const [, version] = embeddedCmpName.split("/v/");
|
|
69
|
+
const calculatedVersion = version || "0_0_1";
|
|
70
|
+
|
|
71
|
+
return { specifier: `aura/interop/v/${calculatedVersion}`, type: "aura" };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
renderedCallback() {
|
|
75
|
+
super.renderedCallback();
|
|
76
|
+
if (this.refs?.embeddedCmp) {
|
|
77
|
+
this._stopRenderActivity();
|
|
78
|
+
|
|
79
|
+
// PDP_ProductUsage telemetry - component is now fully rendered (log only once)
|
|
80
|
+
if (!this.#telemetryLogged) {
|
|
81
|
+
this.#telemetryLogged = true;
|
|
82
|
+
if (this.getInstrumentation().log) {
|
|
83
|
+
this.getInstrumentation().log(pdpEventSchema, {
|
|
84
|
+
eventName: "lightningOutComponent.rendered",
|
|
85
|
+
productFeatureId: "aJCEE000000152T4AQ",
|
|
86
|
+
componentId: this.embeddedCmpId,
|
|
87
|
+
eventVolume: 1,
|
|
88
|
+
contextName: "componentType",
|
|
89
|
+
contextValue: this.embeddedCmpType,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
_handleRenderError(err) {
|
|
97
|
+
this.getInstrumentation().errorRenderActivity(this.#renderActivity, err, {
|
|
98
|
+
...this._getInstrumentationContext(),
|
|
99
|
+
componentProps: JSON.stringify(this.embeddedCmpProps || {}),
|
|
100
|
+
message: err?.message,
|
|
101
|
+
hasError: true,
|
|
102
|
+
});
|
|
103
|
+
this._stopRenderActivity({ hasError: true });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
_stopRenderActivity(details = {}) {
|
|
107
|
+
if (!this.#renderActivity || this.#renderActivityStopped) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
this.getInstrumentation().stopRenderActivity(this.#renderActivity, {
|
|
111
|
+
...this._getInstrumentationContext(),
|
|
112
|
+
hasError: this.isError,
|
|
113
|
+
...details,
|
|
114
|
+
});
|
|
115
|
+
this.#renderActivityStopped = true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
_getInstrumentationContext() {
|
|
119
|
+
return {
|
|
120
|
+
componentName: this.embeddedCmpName,
|
|
121
|
+
componentType: this.embeddedCmpType,
|
|
122
|
+
loAppOrigin: this.getEnv().loAppOrigin,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
package/dist/modules/force/lightningOutLWRAuraContainer/lightningOutLWRAuraContainer.js-meta.xml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!--
|
|
3
|
+
Copyright 2026 Salesforce, Inc. All rights reserved.
|
|
4
|
+
For full license text, see the LICENSE.txt file in this package.
|
|
5
|
+
-->
|
|
6
|
+
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
7
|
+
<isExposed>false</isExposed>
|
|
8
|
+
</LightningComponentBundle>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 salesforce.com, inc.
|
|
3
|
+
* All Rights Reserved
|
|
4
|
+
* Company Confidential
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
CLIENT_ERROR_TYPES,
|
|
9
|
+
onError,
|
|
10
|
+
onHttpError,
|
|
11
|
+
onInvalidSession,
|
|
12
|
+
onUnhandledRejection,
|
|
13
|
+
} from "@lwrjs/globalErrorHandler";
|
|
14
|
+
|
|
15
|
+
export function createErrorHooks(parentPostMessage, logError) {
|
|
16
|
+
function post(type, payload) {
|
|
17
|
+
parentPostMessage({ type: "lo.error", error: { ...payload, type } });
|
|
18
|
+
logError(payload);
|
|
19
|
+
return { handled: true };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
onError((error, type) => post(type, { message: `Error: ${error.message}` }));
|
|
23
|
+
onUnhandledRejection((error, type) =>
|
|
24
|
+
post(type, {
|
|
25
|
+
message: `Unhandled Promise Rejection: ${error.message}`,
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
28
|
+
onInvalidSession((error, type) => post(type, { message: `Invalid Session: ${error.message}` }));
|
|
29
|
+
onHttpError((error, type) => {
|
|
30
|
+
if (type === CLIENT_ERROR_TYPES.HTTP_ERROR) {
|
|
31
|
+
const status = error.status || (error.response && error.response.status);
|
|
32
|
+
if (status) {
|
|
33
|
+
let message = "";
|
|
34
|
+
switch (status) {
|
|
35
|
+
case 401:
|
|
36
|
+
message = "Session expired or unauthorized. Please log in again.";
|
|
37
|
+
break;
|
|
38
|
+
case 429:
|
|
39
|
+
message = "Too many requests. Please try again later.";
|
|
40
|
+
break;
|
|
41
|
+
default:
|
|
42
|
+
if (status >= 500) {
|
|
43
|
+
message = "A server error occurred. Please try again later.";
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
return post(type, {
|
|
48
|
+
status,
|
|
49
|
+
message: `HTTP Error: ${message}`,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { handled: false };
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 salesforce.com, inc.
|
|
3
|
+
* All Rights Reserved
|
|
4
|
+
* Company Confidential
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import disableBeaconGate from "@salesforce/gate/org.salesforce.lo2.lwr.disableBeacon";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @returns {boolean} Checks if the beacon feature is enabled.
|
|
11
|
+
*/
|
|
12
|
+
export function isBeaconEnabled() {
|
|
13
|
+
return !disableBeaconGate.isOpen({ fallback: false });
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Copyright 2026 Salesforce, Inc. All rights reserved.
|
|
3
|
+
For full license text, see the LICENSE.txt file in this package.
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<template lwc:render-mode="light">
|
|
7
|
+
<lwc:component
|
|
8
|
+
lwc:is={embeddedCmpCtor}
|
|
9
|
+
lwc:ref="embeddedCmp"
|
|
10
|
+
lwc:spread={embeddedCmpProps}
|
|
11
|
+
></lwc:component>
|
|
12
|
+
</template>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!--
|
|
3
|
+
Copyright 2026 Salesforce, Inc. All rights reserved.
|
|
4
|
+
For full license text, see the LICENSE.txt file in this package.
|
|
5
|
+
-->
|
|
6
|
+
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
7
|
+
<isExposed>true</isExposed>
|
|
8
|
+
<targets>
|
|
9
|
+
<target>lightningCommunity__Page</target>
|
|
10
|
+
<target>lightningCommunity__Default</target>
|
|
11
|
+
</targets>
|
|
12
|
+
</LightningComponentBundle>
|
package/dist/modules/force/lightningOutOneRuntimeContainer/lightningOutOneRuntimeContainer.html
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Copyright 2026 Salesforce, Inc. All rights reserved.
|
|
3
|
+
For full license text, see the LICENSE.txt file in this package.
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<template lwc:render-mode="light">
|
|
7
|
+
<div lwc:if={includeBeacon}>
|
|
8
|
+
<aura-interop
|
|
9
|
+
descriptor="instrumentation:beacon"
|
|
10
|
+
config={config}
|
|
11
|
+
></aura-interop>
|
|
12
|
+
</div>
|
|
13
|
+
<lwrlex-instrumentation></lwrlex-instrumentation>
|
|
14
|
+
<lwc:component
|
|
15
|
+
lwc:is={embeddedCmpCtor}
|
|
16
|
+
lwc:ref="embeddedCmp"
|
|
17
|
+
lwc:spread={embeddedCmpProps}
|
|
18
|
+
></lwc:component>
|
|
19
|
+
</template>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 salesforce.com, inc.
|
|
3
|
+
* All Rights Reserved
|
|
4
|
+
* Company Confidential
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import LightningOutBaseContainer from "force/lightningOutBaseContainer";
|
|
8
|
+
import { pdpEventSchema } from "o11y_schema/sf_pdp";
|
|
9
|
+
|
|
10
|
+
import { createErrorHooks } from "./utils/errorHandling";
|
|
11
|
+
import { isBeaconEnabled } from "./utils/gater";
|
|
12
|
+
|
|
13
|
+
export default class LightningOutOneRuntimeContainer extends LightningOutBaseContainer {
|
|
14
|
+
#includeBeacon = false;
|
|
15
|
+
#telemetryLogged = false;
|
|
16
|
+
config = {
|
|
17
|
+
usePageTransactions: false,
|
|
18
|
+
disableAvroValidation: false,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
super();
|
|
23
|
+
createErrorHooks(this.getEnv().parentPostMessage, this.getInstrumentation().logError);
|
|
24
|
+
this.#includeBeacon = isBeaconEnabled();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get includeBeacon() {
|
|
28
|
+
return this.#includeBeacon;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
renderedCallback() {
|
|
32
|
+
super.renderedCallback();
|
|
33
|
+
if (this.refs?.embeddedCmp && !this.#telemetryLogged) {
|
|
34
|
+
this.#telemetryLogged = true;
|
|
35
|
+
if (this.getInstrumentation().log) {
|
|
36
|
+
this.getInstrumentation().log(pdpEventSchema, {
|
|
37
|
+
eventName: "lightningOutComponent.rendered",
|
|
38
|
+
productFeatureId: "aJCEE000000152T4AQ",
|
|
39
|
+
componentId: this.embeddedCmpId,
|
|
40
|
+
eventVolume: 1,
|
|
41
|
+
contextName: "componentType",
|
|
42
|
+
contextValue: this.embeddedCmpType,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
resolveEmbeddedCmpSpecifier(embeddedCmpName) {
|
|
49
|
+
const [name, version] = embeddedCmpName.split("/v/");
|
|
50
|
+
const type = name.includes(":") ? "aura" : "lwc";
|
|
51
|
+
const calculatedVersion = version || "0_0_1";
|
|
52
|
+
if (type === "aura") {
|
|
53
|
+
return { specifier: `aura/interop/v/${calculatedVersion}`, type };
|
|
54
|
+
}
|
|
55
|
+
return { specifier: `${name}/v/${calculatedVersion}`, type };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!--
|
|
3
|
+
Copyright 2026 Salesforce, Inc. All rights reserved.
|
|
4
|
+
For full license text, see the LICENSE.txt file in this package.
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
8
|
+
<isExposed>false</isExposed>
|
|
9
|
+
</LightningComponentBundle>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 salesforce.com, inc.
|
|
3
|
+
* All Rights Reserved
|
|
4
|
+
* Company Confidential
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
CLIENT_ERROR_TYPES,
|
|
9
|
+
onError,
|
|
10
|
+
onHttpError,
|
|
11
|
+
onInvalidSession,
|
|
12
|
+
onUnhandledRejection,
|
|
13
|
+
} from "@lwrjs/globalErrorHandler";
|
|
14
|
+
|
|
15
|
+
export function createErrorHooks(parentPostMessage, logError) {
|
|
16
|
+
function post(type, payload) {
|
|
17
|
+
parentPostMessage({ type: "lo.error", error: { ...payload, type } });
|
|
18
|
+
logError(payload);
|
|
19
|
+
return { handled: true };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
onError((error, type) => post(type, { message: `Error: ${error.message}` }));
|
|
23
|
+
onUnhandledRejection((error, type) =>
|
|
24
|
+
post(type, {
|
|
25
|
+
message: `Unhandled Promise Rejection: ${error.message}`,
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
28
|
+
onInvalidSession((error, type) => post(type, { message: `Invalid Session: ${error.message}` }));
|
|
29
|
+
onHttpError((error, type) => {
|
|
30
|
+
if (type === CLIENT_ERROR_TYPES.HTTP_ERROR) {
|
|
31
|
+
const status = error.status || (error.response && error.response.status);
|
|
32
|
+
if (status) {
|
|
33
|
+
let message = "";
|
|
34
|
+
if (status === 401) {
|
|
35
|
+
message = "Session expired or unauthorized. Please log in again.";
|
|
36
|
+
} else if (status === 429) {
|
|
37
|
+
message = "Too many requests. Please try again later.";
|
|
38
|
+
} else if (status >= 500) {
|
|
39
|
+
message = "A server error occurred. Please try again later.";
|
|
40
|
+
}
|
|
41
|
+
return post(type, {
|
|
42
|
+
status,
|
|
43
|
+
message: `HTTP Error: ${message}`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { handled: false };
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2025 salesforce.com, inc.
|
|
3
|
+
* All Rights Reserved
|
|
4
|
+
* Company Confidential
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import disableBeaconGate from "@salesforce/gate/org.salesforce.lo2.lwr.disableBeacon";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @returns {boolean} Checks if the beacon feature is enabled.
|
|
11
|
+
*/
|
|
12
|
+
export function isBeaconEnabled() {
|
|
13
|
+
return !disableBeaconGate.isOpen({ fallback: false });
|
|
14
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@salesforce-ui-embedding/internal-lightning-out-containers",
|
|
3
|
+
"version": "2.2.7-rc.2",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Lightning Out 2.0 Container Components",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE.txt",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/",
|
|
10
|
+
"!dist/__tests__/",
|
|
11
|
+
"!dist/__mocks__/",
|
|
12
|
+
"!dist/*.test.js",
|
|
13
|
+
"!dist/*.map",
|
|
14
|
+
"LICENSE.txt"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "node scripts/build.mjs",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"clean": "rimraf dist"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
}
|
|
24
|
+
}
|