From 5dadb502009513f2ecb81cd82a9f9a2f8cd4b88f Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Thu, 4 Jun 2026 11:17:02 +0200 Subject: [PATCH 01/15] Add new Browser-rum-salesforce package --- .../src/browser/locationChangeObservable.ts | 11 +- .../src/domain/contexts/defaultContext.ts | 2 +- packages/browser-rum-salesforce/LICENSE | 201 +++++++++++++++++ packages/browser-rum-salesforce/README.md | 210 ++++++++++++++++++ packages/browser-rum-salesforce/package.json | 45 ++++ .../src/entries/main.ts | 83 +++++++ packages/browser-rum-salesforce/typedoc.json | 4 + packages/browser-rum-slim/src/entries/main.ts | 2 + yarn.lock | 17 +- 9 files changed, 571 insertions(+), 4 deletions(-) create mode 100644 packages/browser-rum-salesforce/LICENSE create mode 100644 packages/browser-rum-salesforce/README.md create mode 100644 packages/browser-rum-salesforce/package.json create mode 100644 packages/browser-rum-salesforce/src/entries/main.ts create mode 100644 packages/browser-rum-salesforce/typedoc.json diff --git a/packages/browser-rum-core/src/browser/locationChangeObservable.ts b/packages/browser-rum-core/src/browser/locationChangeObservable.ts index fefa7d6ee0..9ec1b56953 100644 --- a/packages/browser-rum-core/src/browser/locationChangeObservable.ts +++ b/packages/browser-rum-core/src/browser/locationChangeObservable.ts @@ -1,4 +1,11 @@ -import { addEventListener, DOM_EVENT, instrumentMethod, Observable, shallowClone } from '@datadog/browser-core' +import { + addEventListener, + DOM_EVENT, + globalObject, + instrumentMethod, + Observable, + shallowClone, +} from '@datadog/browser-core' import type { RumConfiguration } from '../domain/configuration' export interface LocationChange { @@ -7,7 +14,7 @@ export interface LocationChange { } export function createLocationChangeObservable(configuration: RumConfiguration) { - let currentLocation = shallowClone(location) + let currentLocation = shallowClone(globalObject.location) return new Observable((observable) => { const { stop: stopHistoryTracking } = trackHistory(configuration, onLocationChange) diff --git a/packages/browser-rum-core/src/domain/contexts/defaultContext.ts b/packages/browser-rum-core/src/domain/contexts/defaultContext.ts index 1281c9489e..2d5d8056f4 100644 --- a/packages/browser-rum-core/src/domain/contexts/defaultContext.ts +++ b/packages/browser-rum-core/src/domain/contexts/defaultContext.ts @@ -5,7 +5,7 @@ import type { DefaultRumEventAttributes, Hooks } from '../hooks' // replaced at build time declare const __BUILD_ENV__SDK_VERSION__: string -export type SdkName = 'rum' | 'rum-slim' | 'rum-synthetics' +export type SdkName = 'rum' | 'rum-slim' | 'rum-synthetics' | 'rum-salesforce' export function startDefaultContext(hooks: Hooks, configuration: RumConfiguration, sdkName: SdkName | undefined) { hooks.register(HookNames.Assemble, ({ eventType }): DefaultRumEventAttributes => { diff --git a/packages/browser-rum-salesforce/LICENSE b/packages/browser-rum-salesforce/LICENSE new file mode 100644 index 0000000000..e6d7fbc979 --- /dev/null +++ b/packages/browser-rum-salesforce/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2019-Present Datadog, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/browser-rum-salesforce/README.md b/packages/browser-rum-salesforce/README.md new file mode 100644 index 0000000000..b54e423743 --- /dev/null +++ b/packages/browser-rum-salesforce/README.md @@ -0,0 +1,210 @@ +# Salesforce Lightning RUM + +This entrypoint is intended for Salesforce Lightning and LWC applications. + +## Recommended LWC Setup + +### 1. Add the static resource + +Copy `packages/browser-rum-salesforce/bundle/datadog-rum-salesforce.js` into your Salesforce project as `staticresources/datadog_rum_salesforce.js`. + +Create the accompanying metadata file `staticresources/datadog_rum_salesforce.resource-meta.xml`: + +```xml + + + Public + application/javascript + +``` + +### 2. Add the CSP trusted site + +Create a CSP trusted site metadata file under `cspTrustedSites/`, for example `cspTrustedSites/browser_intake_datadoghq_com.cspTrustedSite-meta.xml`: + +```xml + + + false + false + All + Datadog browser RUM intake for US1 + https://browser-intake-datadoghq.com + true + true + false + false + false + false + false + +``` + +### 3. Create the LWC component + +Create a new LWC component under `lwc/datadogInit/`, for example `lwc/datadogInit/datadogInit.js`: + +```js +import { LightningElement, api, wire } from 'lwc' +import { NavigationMixin, CurrentPageReference } from 'lightning/navigation' +import datadogRumSalesforce from '@salesforce/resourceUrl/datadog_rum_salesforce' +import { loadScript } from 'lightning/platformResourceLoader' + +let datadogInitialization +let lastStartedUrl + +export default class DatadogInit extends NavigationMixin(LightningElement) { + @api applicationId + @api clientToken + @api site + @api service + @api env + @api allowedTracingUrls + @api trackViewsManually + + connectedCallback() { + this.initialize() + } + + @wire(CurrentPageReference) + handleCurrentPageReference(pageReference) { + if (!pageReference) { + return + } + + this.initialize() + + if (window.DD_RUM) { + this.startViewForPageReference(pageReference) + } + } + + startViewForPageReference(pageReference) { + const urlPromise = this[NavigationMixin.GenerateUrl](pageReference) + urlPromise.then((url) => { + if (url === lastStartedUrl) { + return + } + lastStartedUrl = url + const absoluteUrl = new URL(url, window.location.origin).href + window.DD_RUM.startView({ name: url, url: absoluteUrl }) + }) + } + + initialize() { + if (!datadogInitialization) { + datadogInitialization = this.loadDatadogRum() + } + } + + loadDatadogRum() { + return loadScript(this, datadogRumSalesforce).then(() => { + const initConfig = { + applicationId: this.applicationId, + clientToken: this.clientToken, + env: this.env, + service: this.service, + site: this.site, + trackViewsManually: true, + trackEarlyRequests: true, + trackLongTasks: true, + trackResources: true, + trackUserInteractions: true, + } + window.DD_RUM.init(initConfig) + lastStartedUrl = window.location.pathname + window.location.search + window.location.hash + window.DD_RUM.startView({ + name: lastStartedUrl, + url: window.location.href, + }) + }) + } +} +``` + +### 4. Add the component to a Lightning App + +Add `datadogInit` to your app's utility bar via a flexipage metadata file. Set `eager` to `true` so the component initializes immediately on page load. + +If your app already has a utility bar, edit its existing flexipage file under `flexipages/`. If it does not, create one — for example `flexipages/MyApp_UtilityBar.flexipage-meta.xml`. + +```xml + + + + + + + applicationId + YOUR_APPLICATION_ID + + + clientToken + YOUR_CLIENT_TOKEN + + + site + datadoghq.com + + + service + your-service-name + + + env + production + + + eager + decorator + true + + datadogInit + datadogInit + + + utilityItems + Region + + + +``` + +## Salesforce Feature Support + +| Feature | Supported | +| ---------------------- | ------------------------------ | +| **View Events** | | +| Initial View | ✅ | +| Manual Tracking | ✅ | +| Navigation Timings | ✅ | +| Web Vitals | ✅ | +| Automatic Tracking | ✅⚠️ Supported with workaround | +| Loading Time | ✅⚠️(1) | +| **Resource Events** | | +| Fetch Resources | ✅⚠️(2) | +| XHR Resources | ✅⚠️(2) | +| Other Resources | ✅ | +| APM Correlation | ✅⚠️(2) | +| **Action Events** | | +| Custom Actions | ✅ | +| Click Actions | ✅ | +| Frustration Signals | ✅ | +| Selectors | ✅⚠️(3) | +| Action Name | ✅⚠️(2) | +| Loading Time | ✅⚠️(1) | +| **Error Events** | | +| Console Error | ✅ | +| Custom Errors | ✅ | +| Runtime Errors | ✅⚠️(4) | +| `onUnhandledRejection` | ❌ | +| CSP Violation | ❌ | +| **Other** | | +| Vital Events | ✅ | +| Long Task Events | ✅ | +| Session Replay | ❌ | + +1. Loading time ends when no pending network requests are detected. Under LWS the SDK may not observe all pending fetch/XHR requests, causing loading time to end prematurely or be absent. +2. Events are collected, but `beforeSend` context will not include the response payload (`context.response`, `context.xhr`, `context.responseBody`) as these objects are inaccessible within the LWS sandbox. +3. Due to shadow boundaries, the SDK might receive the component host as `event.target` instead of the actual clicked element. +4. Direct synchronous errors can reach the SDK through `window.onerror`, but in the Lightning shell they may be redacted as "Script error." with no original error object, URL, line, or stack available to the SDK. diff --git a/packages/browser-rum-salesforce/package.json b/packages/browser-rum-salesforce/package.json new file mode 100644 index 0000000000..b1d51a98ec --- /dev/null +++ b/packages/browser-rum-salesforce/package.json @@ -0,0 +1,45 @@ +{ + "name": "@datadog/browser-rum-salesforce", + "private": true, + "license": "Apache-2.0", + "main": "cjs/entries/main.js", + "module": "esm/entries/main.js", + "types": "cjs/entries/main.d.ts", + "files": [ + "bundle/**/*.js", + "cjs", + "esm", + "src", + "!src/**/*.spec.ts", + "!src/**/*.specHelper.ts" + ], + "scripts": { + "build": "node ../../scripts/build/build-package.ts --modules --bundle datadog-rum-salesforce.js", + "build:bundle": "node ../../scripts/build/build-package.ts --bundle datadog-rum-salesforce.js", + "prepack": "yarn build" + }, + "dependencies": { + "@datadog/browser-core": "7.2.0", + "@datadog/browser-rum-core": "7.2.0", + "@datadog/browser-rum-slim": "7.2.0" + }, + "peerDependencies": { + "@datadog/browser-logs": "7.2.0" + }, + "peerDependenciesMeta": { + "@datadog/browser-logs": { + "optional": true + } + }, + "repository": { + "type": "git", + "url": "https://github.com/DataDog/browser-sdk.git", + "directory": "packages/browser-rum-salesforce" + }, + "volta": { + "extends": "../../package.json" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/browser-rum-salesforce/src/entries/main.ts b/packages/browser-rum-salesforce/src/entries/main.ts new file mode 100644 index 0000000000..5baf7e8ca6 --- /dev/null +++ b/packages/browser-rum-salesforce/src/entries/main.ts @@ -0,0 +1,83 @@ +// Keep the following in sync with packages/browser-rum-slim/src/entries/main.ts +import { defineGlobal, globalObject } from '@datadog/browser-core' +import type { RumPublicApi } from '@datadog/browser-rum-core' +import { makeRumPublicApi } from '@datadog/browser-rum-core' +import { makeRecorderApiStub, makeProfilerApiStub } from '@datadog/browser-rum-slim' + +export type { + User, + Account, + TraceContextInjection, + SessionPersistence, + TrackingConsent, + MatchOption, + ProxyFn, + Site, + Context, + ContextValue, + ContextArray, + RumInternalContext, +} from '@datadog/browser-core' + +/** + * @deprecated Use {@link DatadogRum} instead + */ +export type RumGlobal = RumPublicApi + +export type { + RumPublicApi as DatadogRum, + RumInitConfiguration, + RumBeforeSend, + ViewOptions, + StartRecordingOptions, + AddDurationVitalOptions, + DurationVitalOptions, + FeatureOperationOptions, + FailureReason, + ActionOptions, + ResourceOptions, + ResourceStopOptions, + TracingOption, + RumPlugin, + OnRumStartOptions, + PropagatorType, + FeatureFlagsForEvents, + MatchHeader, + + // Events + CommonProperties, + RumEvent, + RumActionEvent, + RumErrorEvent, + RumLongTaskEvent, + RumResourceEvent, + RumViewEvent, + RumVitalEvent, + + // Events context + RumEventDomainContext, + RumViewEventDomainContext, + RumErrorEventDomainContext, + RumActionEventDomainContext, + RumVitalEventDomainContext, + RumResourceEventDomainContext, + RumLongTaskEventDomainContext, +} from '@datadog/browser-rum-core' +export { DEFAULT_TRACKED_RESOURCE_HEADERS } from '@datadog/browser-rum-core' +export { DefaultPrivacyLevel } from '@datadog/browser-core' + +/** + * The global RUM instance. Use this to call RUM methods. + * + * @category Main + * @see {@link DatadogRum} + * @see [RUM Browser Monitoring Setup](https://docs.datadoghq.com/real_user_monitoring/browser/) + */ +export const datadogRum = makeRumPublicApi(makeRecorderApiStub(), makeProfilerApiStub(), { + sdkName: 'rum-salesforce', +}) + +interface BrowserWindow { + DD_RUM?: RumPublicApi +} +defineGlobal(globalObject as BrowserWindow, 'DD_RUM', datadogRum) diff --git a/packages/browser-rum-salesforce/typedoc.json b/packages/browser-rum-salesforce/typedoc.json new file mode 100644 index 0000000000..002b26a53c --- /dev/null +++ b/packages/browser-rum-salesforce/typedoc.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "entryPoints": ["src/entries/main.ts"] +} diff --git a/packages/browser-rum-slim/src/entries/main.ts b/packages/browser-rum-slim/src/entries/main.ts index 1668347f85..4b0f23cd8e 100644 --- a/packages/browser-rum-slim/src/entries/main.ts +++ b/packages/browser-rum-slim/src/entries/main.ts @@ -4,6 +4,8 @@ import type { RumPublicApi } from '@datadog/browser-rum-core' import { makeRumPublicApi } from '@datadog/browser-rum-core' import { makeRecorderApiStub } from '../boot/stubRecorderApi' import { makeProfilerApiStub } from '../boot/stubProfilerApi' +export { makeRecorderApiStub } from '../boot/stubRecorderApi' +export { makeProfilerApiStub } from '../boot/stubProfilerApi' export type { User, diff --git a/yarn.lock b/yarn.lock index 0da983be0f..03f7edc5e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -494,7 +494,22 @@ __metadata: languageName: unknown linkType: soft -"@datadog/browser-rum-slim@workspace:packages/browser-rum-slim": +"@datadog/browser-rum-salesforce@workspace:packages/browser-rum-salesforce": + version: 0.0.0-use.local + resolution: "@datadog/browser-rum-salesforce@workspace:packages/browser-rum-salesforce" + dependencies: + "@datadog/browser-core": "npm:7.2.0" + "@datadog/browser-rum-core": "npm:7.2.0" + "@datadog/browser-rum-slim": "npm:7.2.0" + peerDependencies: + "@datadog/browser-logs": 7.2.0 + peerDependenciesMeta: + "@datadog/browser-logs": + optional: true + languageName: unknown + linkType: soft + +"@datadog/browser-rum-slim@npm:7.2.0, @datadog/browser-rum-slim@workspace:packages/browser-rum-slim": version: 0.0.0-use.local resolution: "@datadog/browser-rum-slim@workspace:packages/browser-rum-slim" dependencies: From 8c6cd15706cd6d398c745f95f48d8bc5b0bd858f Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Thu, 4 Jun 2026 11:26:42 +0200 Subject: [PATCH 02/15] Add support for Salesforce LWS --- eslint-local-rules/disallowSideEffects.ts | 1 + .../browser-core/src/browser/addEventListener.ts | 14 ++++++++++++++ packages/browser-core/src/browser/cookieAccess.ts | 8 +++++++- .../src/domain/error/trackRuntimeError.ts | 7 +++++++ .../src/domain/report/reportObservable.ts | 8 +++++++- .../session/storeStrategies/sessionInCookie.ts | 7 ++----- packages/browser-core/src/index.ts | 1 + .../src/browser/cookieObservable.ts | 3 ++- .../src/domain/contexts/urlContexts.ts | 3 ++- 9 files changed, 43 insertions(+), 9 deletions(-) diff --git a/eslint-local-rules/disallowSideEffects.ts b/eslint-local-rules/disallowSideEffects.ts index 3a8783b2b1..7232cc0512 100644 --- a/eslint-local-rules/disallowSideEffects.ts +++ b/eslint-local-rules/disallowSideEffects.ts @@ -40,6 +40,7 @@ const pathsWithSideEffect = new Set([ `${packagesRoot}/browser-logs/src/entries/main.ts`, `${packagesRoot}/browser-rum/src/entries/main.ts`, `${packagesRoot}/browser-rum-slim/src/entries/main.ts`, + `${packagesRoot}/browser-rum-salesforce/src/entries/main.ts`, `${packagesRoot}/browser-debugger/src/entries/main.ts`, ]) diff --git a/packages/browser-core/src/browser/addEventListener.ts b/packages/browser-core/src/browser/addEventListener.ts index 89d135f0ff..90e813c175 100644 --- a/packages/browser-core/src/browser/addEventListener.ts +++ b/packages/browser-core/src/browser/addEventListener.ts @@ -39,6 +39,7 @@ export const enum DOM_EVENT { SECURITY_POLICY_VIOLATION = 'securitypolicyviolation', SELECTION_CHANGE = 'selectionchange', STORAGE = 'storage', + UNHANDLED_REJECTION = 'unhandledrejection', } interface AddEventListenerOptions { @@ -146,3 +147,16 @@ export function addEventListeners & string>( + eventTarget: Target, + eventName: EventName, + listener: (event: EventMapFor[EventName] & { type: EventName }) => void +) { + try { + addEventListener({}, eventTarget, eventName, listener).stop() + return true + } catch { + return false + } +} diff --git a/packages/browser-core/src/browser/cookieAccess.ts b/packages/browser-core/src/browser/cookieAccess.ts index bfc9953727..03fc4fc671 100644 --- a/packages/browser-core/src/browser/cookieAccess.ts +++ b/packages/browser-core/src/browser/cookieAccess.ts @@ -4,10 +4,11 @@ import { Observable } from '../tools/observable' import { mockable } from '../tools/mockable' import { display } from '../tools/display' import { generateUUID } from '../tools/utils/stringUtils' +import { noop } from '../tools/utils/functionUtils' import type { Configuration } from '../domain/configuration' import { addTelemetryDebug } from '../domain/telemetry' import { globalObject } from '../tools/globalObject' -import { addEventListener, DOM_EVENT } from './addEventListener' +import { addEventListener, DOM_EVENT, isEventSupported } from './addEventListener' import { getCookies, setCookie } from './cookie' import type { CookieOptions } from './cookie' @@ -162,3 +163,8 @@ export function createDocumentCookieAccess( observable, } } + +// Salesforce LWS does not support the change event of CookieStore objects. https://developer.salesforce.com/tools/lws-distortion-viewer +export function isCookieStoreSupported(): boolean { + return Boolean(globalObject.cookieStore && isEventSupported(globalObject.cookieStore, DOM_EVENT.CHANGE, noop)) +} diff --git a/packages/browser-core/src/domain/error/trackRuntimeError.ts b/packages/browser-core/src/domain/error/trackRuntimeError.ts index f9b42b37b8..c3dbe2020c 100644 --- a/packages/browser-core/src/domain/error/trackRuntimeError.ts +++ b/packages/browser-core/src/domain/error/trackRuntimeError.ts @@ -4,6 +4,8 @@ import { clocksNow } from '../../tools/utils/timeUtils' import type { StackTrace } from '../../tools/stackTrace/computeStackTrace' import { computeStackTraceFromOnErrorMessage } from '../../tools/stackTrace/computeStackTrace' import { globalObject } from '../../tools/globalObject' +import { DOM_EVENT, isEventSupported } from '../../browser/addEventListener' +import { noop } from '../../tools/utils/functionUtils' import { computeRawError, isError } from './error' import type { RawError } from './error.types' import { ErrorHandling, ErrorSource, NonErrorPrefix } from './error.types' @@ -44,6 +46,11 @@ export function instrumentOnError(callback: UnhandledErrorCallback) { } export function instrumentUnhandledRejection(callback: UnhandledErrorCallback) { + // Salesforce LWS does not support the unhandledrejection event. https://developer.salesforce.com/tools/lws-distortion-viewer + if (!isEventSupported(globalObject.window, DOM_EVENT.UNHANDLED_REJECTION, noop)) { + return { stop: noop } + } + return instrumentMethod(globalObject, 'onunhandledrejection', ({ parameters: [e] }) => { callback(e.reason || 'Empty reason') }) diff --git a/packages/browser-core/src/domain/report/reportObservable.ts b/packages/browser-core/src/domain/report/reportObservable.ts index 282c5fc935..583e56317e 100644 --- a/packages/browser-core/src/domain/report/reportObservable.ts +++ b/packages/browser-core/src/domain/report/reportObservable.ts @@ -1,7 +1,8 @@ import { toStackTraceString } from '../../tools/stackTrace/handlingStack' import { monitor } from '../../tools/monitor' import { mergeObservables, Observable } from '../../tools/observable' -import { addEventListener, DOM_EVENT } from '../../browser/addEventListener' +import { addEventListener, DOM_EVENT, isEventSupported } from '../../browser/addEventListener' +import { noop } from '../../tools/utils/functionUtils' import { safeTruncate } from '../../tools/utils/stringUtils' import type { Configuration } from '../configuration' import type { RawError } from '../error/error.types' @@ -60,6 +61,11 @@ function createReportObservable(reportTypes: ReportType[]) { function createCspViolationReportObservable(configuration: Configuration) { return new Observable((observable) => { + // Salesforce does not allow to add a securitypolicyviolation event listener. https://developer.salesforce.com/tools/lws-distortion-viewer + if (!isEventSupported(document, DOM_EVENT.SECURITY_POLICY_VIOLATION, noop)) { + return + } + const { stop } = addEventListener(configuration, document, DOM_EVENT.SECURITY_POLICY_VIOLATION, (event) => { observable.notify(buildRawReportErrorFromCspViolation(event)) }) diff --git a/packages/browser-core/src/domain/session/storeStrategies/sessionInCookie.ts b/packages/browser-core/src/domain/session/storeStrategies/sessionInCookie.ts index 75945e6928..594c4063e5 100644 --- a/packages/browser-core/src/domain/session/storeStrategies/sessionInCookie.ts +++ b/packages/browser-core/src/domain/session/storeStrategies/sessionInCookie.ts @@ -13,8 +13,8 @@ import { areCookiesAuthorized, createCookieStoreAccess, createDocumentCookieAccess, + isCookieStoreSupported, } from '../../../browser/cookieAccess' -import { globalObject } from '../../../tools/globalObject' import { CookieApi, LEGACY_SESSION_STORE_KEY, SESSION_STORE_KEY } from './sessionStoreStrategy' import type { SessionStoreStrategy, @@ -33,10 +33,7 @@ export async function selectCookieStrategy( return undefined } - if ( - mockable(globalObject.cookieStore) && - (await areCookiesAuthorized(createCookieStoreAccess, cookieOptions, configuration)) - ) { + if (isCookieStoreSupported() && (await areCookiesAuthorized(createCookieStoreAccess, cookieOptions, configuration))) { return { type: SessionPersistence.COOKIE, cookieOptions, cookieApi: CookieApi.COOKIE_STORE } } diff --git a/packages/browser-core/src/index.ts b/packages/browser-core/src/index.ts index bbf50c05f5..c35a824d0d 100644 --- a/packages/browser-core/src/index.ts +++ b/packages/browser-core/src/index.ts @@ -103,6 +103,7 @@ export { export { NonErrorPrefix } from './domain/error/error.types' export type { Context, ContextArray, ContextValue } from './tools/serialisation/context' export { getCookie, getInitCookie, setCookie, deleteCookie, resetInitCookies } from './browser/cookie' +export { isCookieStoreSupported } from './browser/cookieAccess' export type { CookieStore, WeakRef, diff --git a/packages/browser-rum-core/src/browser/cookieObservable.ts b/packages/browser-rum-core/src/browser/cookieObservable.ts index 652f6ad703..4e0a72329f 100644 --- a/packages/browser-rum-core/src/browser/cookieObservable.ts +++ b/packages/browser-rum-core/src/browser/cookieObservable.ts @@ -8,12 +8,13 @@ import { DOM_EVENT, getCookie, globalObject, + isCookieStoreSupported, } from '@datadog/browser-core' export type CookieObservable = ReturnType export function createCookieObservable(configuration: Configuration, cookieName: string) { - const detectCookieChangeStrategy = globalObject.cookieStore + const detectCookieChangeStrategy = isCookieStoreSupported() ? listenToCookieStoreChange(configuration) : watchCookieFallback diff --git a/packages/browser-rum-core/src/domain/contexts/urlContexts.ts b/packages/browser-rum-core/src/domain/contexts/urlContexts.ts index 530172a84c..91f72c92c3 100644 --- a/packages/browser-rum-core/src/domain/contexts/urlContexts.ts +++ b/packages/browser-rum-core/src/domain/contexts/urlContexts.ts @@ -7,6 +7,7 @@ import { DISCARDED, mockable, buildUrl, + globalObject, } from '@datadog/browser-core' import type { LocationChange } from '../../browser/locationChangeObservable' import type { LifeCycle } from '../lifeCycle' @@ -41,7 +42,7 @@ export function startUrlContexts( let previousViewUrl: string | undefined lifeCycle.subscribe(LifeCycleEventType.BEFORE_VIEW_CREATED, ({ startClocks, url }) => { - const locationHref = mockable(location).href + const locationHref = mockable(globalObject.location).href const viewUrl = url !== undefined ? buildUrl(url, locationHref).href : locationHref urlContextHistory.add( buildUrlContext({ From 48f1282cc2f522e7b14d1b123f8d3c1879d81170 Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Thu, 4 Jun 2026 17:39:27 +0200 Subject: [PATCH 03/15] Address comments from review --- eslint-local-rules/disallowSideEffects.ts | 1 - .../src/browser/addEventListener.ts | 6 +- .../browser-core/src/browser/cookieAccess.ts | 2 +- .../src/domain/error/trackRuntimeError.ts | 2 +- .../src/domain/report/reportObservable.ts | 2 +- packages/browser-rum-salesforce/LICENSE | 201 ------------------ packages/browser-rum-salesforce/package.json | 45 ---- .../src/entries/main.ts | 83 -------- packages/browser-rum-salesforce/typedoc.json | 4 - packages/browser-rum-slim/src/entries/main.ts | 2 - .../src/salesforce}/README.md | 0 yarn.lock | 17 +- 12 files changed, 7 insertions(+), 358 deletions(-) delete mode 100644 packages/browser-rum-salesforce/LICENSE delete mode 100644 packages/browser-rum-salesforce/package.json delete mode 100644 packages/browser-rum-salesforce/src/entries/main.ts delete mode 100644 packages/browser-rum-salesforce/typedoc.json rename packages/{browser-rum-salesforce => browser-rum-slim/src/salesforce}/README.md (100%) diff --git a/eslint-local-rules/disallowSideEffects.ts b/eslint-local-rules/disallowSideEffects.ts index 7232cc0512..3a8783b2b1 100644 --- a/eslint-local-rules/disallowSideEffects.ts +++ b/eslint-local-rules/disallowSideEffects.ts @@ -40,7 +40,6 @@ const pathsWithSideEffect = new Set([ `${packagesRoot}/browser-logs/src/entries/main.ts`, `${packagesRoot}/browser-rum/src/entries/main.ts`, `${packagesRoot}/browser-rum-slim/src/entries/main.ts`, - `${packagesRoot}/browser-rum-salesforce/src/entries/main.ts`, `${packagesRoot}/browser-debugger/src/entries/main.ts`, ]) diff --git a/packages/browser-core/src/browser/addEventListener.ts b/packages/browser-core/src/browser/addEventListener.ts index 90e813c175..bc79108c38 100644 --- a/packages/browser-core/src/browser/addEventListener.ts +++ b/packages/browser-core/src/browser/addEventListener.ts @@ -1,5 +1,6 @@ import { monitor } from '../tools/monitor' import { getZoneJsOriginalValue } from '../tools/getZoneJsOriginalValue' +import { noop } from '../tools/utils/functionUtils' import type { CookieStore, CookieStoreEventMap, VisualViewport, VisualViewportEventMap } from './browser.types' export type TrustableEvent = E & { __ddIsTrusted?: boolean } @@ -150,11 +151,10 @@ export function addEventListeners & string>( eventTarget: Target, - eventName: EventName, - listener: (event: EventMapFor[EventName] & { type: EventName }) => void + eventName: EventName ) { try { - addEventListener({}, eventTarget, eventName, listener).stop() + addEventListener({}, eventTarget, eventName, noop).stop() return true } catch { return false diff --git a/packages/browser-core/src/browser/cookieAccess.ts b/packages/browser-core/src/browser/cookieAccess.ts index 03fc4fc671..b801276901 100644 --- a/packages/browser-core/src/browser/cookieAccess.ts +++ b/packages/browser-core/src/browser/cookieAccess.ts @@ -166,5 +166,5 @@ export function createDocumentCookieAccess( // Salesforce LWS does not support the change event of CookieStore objects. https://developer.salesforce.com/tools/lws-distortion-viewer export function isCookieStoreSupported(): boolean { - return Boolean(globalObject.cookieStore && isEventSupported(globalObject.cookieStore, DOM_EVENT.CHANGE, noop)) + return Boolean(globalObject.cookieStore && isEventSupported(globalObject.cookieStore, DOM_EVENT.CHANGE)) } diff --git a/packages/browser-core/src/domain/error/trackRuntimeError.ts b/packages/browser-core/src/domain/error/trackRuntimeError.ts index c3dbe2020c..6599d2bfd9 100644 --- a/packages/browser-core/src/domain/error/trackRuntimeError.ts +++ b/packages/browser-core/src/domain/error/trackRuntimeError.ts @@ -47,7 +47,7 @@ export function instrumentOnError(callback: UnhandledErrorCallback) { export function instrumentUnhandledRejection(callback: UnhandledErrorCallback) { // Salesforce LWS does not support the unhandledrejection event. https://developer.salesforce.com/tools/lws-distortion-viewer - if (!isEventSupported(globalObject.window, DOM_EVENT.UNHANDLED_REJECTION, noop)) { + if (!isEventSupported(window, DOM_EVENT.UNHANDLED_REJECTION)) { return { stop: noop } } diff --git a/packages/browser-core/src/domain/report/reportObservable.ts b/packages/browser-core/src/domain/report/reportObservable.ts index 583e56317e..e39feee99b 100644 --- a/packages/browser-core/src/domain/report/reportObservable.ts +++ b/packages/browser-core/src/domain/report/reportObservable.ts @@ -62,7 +62,7 @@ function createReportObservable(reportTypes: ReportType[]) { function createCspViolationReportObservable(configuration: Configuration) { return new Observable((observable) => { // Salesforce does not allow to add a securitypolicyviolation event listener. https://developer.salesforce.com/tools/lws-distortion-viewer - if (!isEventSupported(document, DOM_EVENT.SECURITY_POLICY_VIOLATION, noop)) { + if (!isEventSupported(document, DOM_EVENT.SECURITY_POLICY_VIOLATION)) { return } diff --git a/packages/browser-rum-salesforce/LICENSE b/packages/browser-rum-salesforce/LICENSE deleted file mode 100644 index e6d7fbc979..0000000000 --- a/packages/browser-rum-salesforce/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2019-Present Datadog, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/browser-rum-salesforce/package.json b/packages/browser-rum-salesforce/package.json deleted file mode 100644 index b1d51a98ec..0000000000 --- a/packages/browser-rum-salesforce/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@datadog/browser-rum-salesforce", - "private": true, - "license": "Apache-2.0", - "main": "cjs/entries/main.js", - "module": "esm/entries/main.js", - "types": "cjs/entries/main.d.ts", - "files": [ - "bundle/**/*.js", - "cjs", - "esm", - "src", - "!src/**/*.spec.ts", - "!src/**/*.specHelper.ts" - ], - "scripts": { - "build": "node ../../scripts/build/build-package.ts --modules --bundle datadog-rum-salesforce.js", - "build:bundle": "node ../../scripts/build/build-package.ts --bundle datadog-rum-salesforce.js", - "prepack": "yarn build" - }, - "dependencies": { - "@datadog/browser-core": "7.2.0", - "@datadog/browser-rum-core": "7.2.0", - "@datadog/browser-rum-slim": "7.2.0" - }, - "peerDependencies": { - "@datadog/browser-logs": "7.2.0" - }, - "peerDependenciesMeta": { - "@datadog/browser-logs": { - "optional": true - } - }, - "repository": { - "type": "git", - "url": "https://github.com/DataDog/browser-sdk.git", - "directory": "packages/browser-rum-salesforce" - }, - "volta": { - "extends": "../../package.json" - }, - "publishConfig": { - "access": "public" - } -} diff --git a/packages/browser-rum-salesforce/src/entries/main.ts b/packages/browser-rum-salesforce/src/entries/main.ts deleted file mode 100644 index 5baf7e8ca6..0000000000 --- a/packages/browser-rum-salesforce/src/entries/main.ts +++ /dev/null @@ -1,83 +0,0 @@ -// Keep the following in sync with packages/browser-rum-slim/src/entries/main.ts -import { defineGlobal, globalObject } from '@datadog/browser-core' -import type { RumPublicApi } from '@datadog/browser-rum-core' -import { makeRumPublicApi } from '@datadog/browser-rum-core' -import { makeRecorderApiStub, makeProfilerApiStub } from '@datadog/browser-rum-slim' - -export type { - User, - Account, - TraceContextInjection, - SessionPersistence, - TrackingConsent, - MatchOption, - ProxyFn, - Site, - Context, - ContextValue, - ContextArray, - RumInternalContext, -} from '@datadog/browser-core' - -/** - * @deprecated Use {@link DatadogRum} instead - */ -export type RumGlobal = RumPublicApi - -export type { - RumPublicApi as DatadogRum, - RumInitConfiguration, - RumBeforeSend, - ViewOptions, - StartRecordingOptions, - AddDurationVitalOptions, - DurationVitalOptions, - FeatureOperationOptions, - FailureReason, - ActionOptions, - ResourceOptions, - ResourceStopOptions, - TracingOption, - RumPlugin, - OnRumStartOptions, - PropagatorType, - FeatureFlagsForEvents, - MatchHeader, - - // Events - CommonProperties, - RumEvent, - RumActionEvent, - RumErrorEvent, - RumLongTaskEvent, - RumResourceEvent, - RumViewEvent, - RumVitalEvent, - - // Events context - RumEventDomainContext, - RumViewEventDomainContext, - RumErrorEventDomainContext, - RumActionEventDomainContext, - RumVitalEventDomainContext, - RumResourceEventDomainContext, - RumLongTaskEventDomainContext, -} from '@datadog/browser-rum-core' -export { DEFAULT_TRACKED_RESOURCE_HEADERS } from '@datadog/browser-rum-core' -export { DefaultPrivacyLevel } from '@datadog/browser-core' - -/** - * The global RUM instance. Use this to call RUM methods. - * - * @category Main - * @see {@link DatadogRum} - * @see [RUM Browser Monitoring Setup](https://docs.datadoghq.com/real_user_monitoring/browser/) - */ -export const datadogRum = makeRumPublicApi(makeRecorderApiStub(), makeProfilerApiStub(), { - sdkName: 'rum-salesforce', -}) - -interface BrowserWindow { - DD_RUM?: RumPublicApi -} -defineGlobal(globalObject as BrowserWindow, 'DD_RUM', datadogRum) diff --git a/packages/browser-rum-salesforce/typedoc.json b/packages/browser-rum-salesforce/typedoc.json deleted file mode 100644 index 002b26a53c..0000000000 --- a/packages/browser-rum-salesforce/typedoc.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://typedoc.org/schema.json", - "entryPoints": ["src/entries/main.ts"] -} diff --git a/packages/browser-rum-slim/src/entries/main.ts b/packages/browser-rum-slim/src/entries/main.ts index 4b0f23cd8e..1668347f85 100644 --- a/packages/browser-rum-slim/src/entries/main.ts +++ b/packages/browser-rum-slim/src/entries/main.ts @@ -4,8 +4,6 @@ import type { RumPublicApi } from '@datadog/browser-rum-core' import { makeRumPublicApi } from '@datadog/browser-rum-core' import { makeRecorderApiStub } from '../boot/stubRecorderApi' import { makeProfilerApiStub } from '../boot/stubProfilerApi' -export { makeRecorderApiStub } from '../boot/stubRecorderApi' -export { makeProfilerApiStub } from '../boot/stubProfilerApi' export type { User, diff --git a/packages/browser-rum-salesforce/README.md b/packages/browser-rum-slim/src/salesforce/README.md similarity index 100% rename from packages/browser-rum-salesforce/README.md rename to packages/browser-rum-slim/src/salesforce/README.md diff --git a/yarn.lock b/yarn.lock index 03f7edc5e4..0da983be0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -494,22 +494,7 @@ __metadata: languageName: unknown linkType: soft -"@datadog/browser-rum-salesforce@workspace:packages/browser-rum-salesforce": - version: 0.0.0-use.local - resolution: "@datadog/browser-rum-salesforce@workspace:packages/browser-rum-salesforce" - dependencies: - "@datadog/browser-core": "npm:7.2.0" - "@datadog/browser-rum-core": "npm:7.2.0" - "@datadog/browser-rum-slim": "npm:7.2.0" - peerDependencies: - "@datadog/browser-logs": 7.2.0 - peerDependenciesMeta: - "@datadog/browser-logs": - optional: true - languageName: unknown - linkType: soft - -"@datadog/browser-rum-slim@npm:7.2.0, @datadog/browser-rum-slim@workspace:packages/browser-rum-slim": +"@datadog/browser-rum-slim@workspace:packages/browser-rum-slim": version: 0.0.0-use.local resolution: "@datadog/browser-rum-slim@workspace:packages/browser-rum-slim" dependencies: From 0fe7170344d8fe8a4eaacff53cebccf5ea99d347 Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Thu, 4 Jun 2026 18:09:40 +0200 Subject: [PATCH 04/15] Address comments from Codex --- .../browser-core/src/browser/cookieAccess.ts | 4 ++-- .../src/domain/error/trackRuntimeError.ts | 2 +- .../src/domain/report/reportObservable.ts | 1 - .../src/domain/contexts/defaultContext.ts | 2 +- .../test/locationChangeSetup.ts | 6 +++--- .../browser-rum-slim/src/salesforce/README.md | 21 +++++++++++++++---- 6 files changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/browser-core/src/browser/cookieAccess.ts b/packages/browser-core/src/browser/cookieAccess.ts index b801276901..7723157210 100644 --- a/packages/browser-core/src/browser/cookieAccess.ts +++ b/packages/browser-core/src/browser/cookieAccess.ts @@ -4,7 +4,6 @@ import { Observable } from '../tools/observable' import { mockable } from '../tools/mockable' import { display } from '../tools/display' import { generateUUID } from '../tools/utils/stringUtils' -import { noop } from '../tools/utils/functionUtils' import type { Configuration } from '../domain/configuration' import { addTelemetryDebug } from '../domain/telemetry' import { globalObject } from '../tools/globalObject' @@ -166,5 +165,6 @@ export function createDocumentCookieAccess( // Salesforce LWS does not support the change event of CookieStore objects. https://developer.salesforce.com/tools/lws-distortion-viewer export function isCookieStoreSupported(): boolean { - return Boolean(globalObject.cookieStore && isEventSupported(globalObject.cookieStore, DOM_EVENT.CHANGE)) + const cookieStore = mockable(globalObject.cookieStore) + return Boolean(cookieStore && isEventSupported(cookieStore, DOM_EVENT.CHANGE)) } diff --git a/packages/browser-core/src/domain/error/trackRuntimeError.ts b/packages/browser-core/src/domain/error/trackRuntimeError.ts index 6599d2bfd9..050a8e3772 100644 --- a/packages/browser-core/src/domain/error/trackRuntimeError.ts +++ b/packages/browser-core/src/domain/error/trackRuntimeError.ts @@ -47,7 +47,7 @@ export function instrumentOnError(callback: UnhandledErrorCallback) { export function instrumentUnhandledRejection(callback: UnhandledErrorCallback) { // Salesforce LWS does not support the unhandledrejection event. https://developer.salesforce.com/tools/lws-distortion-viewer - if (!isEventSupported(window, DOM_EVENT.UNHANDLED_REJECTION)) { + if (!isEventSupported(globalObject.window, DOM_EVENT.UNHANDLED_REJECTION)) { return { stop: noop } } diff --git a/packages/browser-core/src/domain/report/reportObservable.ts b/packages/browser-core/src/domain/report/reportObservable.ts index e39feee99b..380802dea8 100644 --- a/packages/browser-core/src/domain/report/reportObservable.ts +++ b/packages/browser-core/src/domain/report/reportObservable.ts @@ -2,7 +2,6 @@ import { toStackTraceString } from '../../tools/stackTrace/handlingStack' import { monitor } from '../../tools/monitor' import { mergeObservables, Observable } from '../../tools/observable' import { addEventListener, DOM_EVENT, isEventSupported } from '../../browser/addEventListener' -import { noop } from '../../tools/utils/functionUtils' import { safeTruncate } from '../../tools/utils/stringUtils' import type { Configuration } from '../configuration' import type { RawError } from '../error/error.types' diff --git a/packages/browser-rum-core/src/domain/contexts/defaultContext.ts b/packages/browser-rum-core/src/domain/contexts/defaultContext.ts index 2d5d8056f4..1281c9489e 100644 --- a/packages/browser-rum-core/src/domain/contexts/defaultContext.ts +++ b/packages/browser-rum-core/src/domain/contexts/defaultContext.ts @@ -5,7 +5,7 @@ import type { DefaultRumEventAttributes, Hooks } from '../hooks' // replaced at build time declare const __BUILD_ENV__SDK_VERSION__: string -export type SdkName = 'rum' | 'rum-slim' | 'rum-synthetics' | 'rum-salesforce' +export type SdkName = 'rum' | 'rum-slim' | 'rum-synthetics' export function startDefaultContext(hooks: Hooks, configuration: RumConfiguration, sdkName: SdkName | undefined) { hooks.register(HookNames.Assemble, ({ eventType }): DefaultRumEventAttributes => { diff --git a/packages/browser-rum-core/test/locationChangeSetup.ts b/packages/browser-rum-core/test/locationChangeSetup.ts index d5d882d293..089aaecfd6 100644 --- a/packages/browser-rum-core/test/locationChangeSetup.ts +++ b/packages/browser-rum-core/test/locationChangeSetup.ts @@ -1,12 +1,12 @@ import { buildLocation, replaceMockable } from '@datadog/browser-core/test' -import { Observable } from '@datadog/browser-core' +import { globalObject, Observable } from '@datadog/browser-core' import type { LocationChange } from '../src/browser/locationChangeObservable' export function setupLocationObserver(initialLocation?: string) { - const fakeLocation = initialLocation ? buildLocation(initialLocation) : location + const fakeLocation = initialLocation ? buildLocation(initialLocation) : globalObject.location const locationChangeObservable = new Observable() - replaceMockable(location, fakeLocation) + replaceMockable(globalObject.location, fakeLocation) function changeLocation(to: string) { const currentLocation = { ...fakeLocation } diff --git a/packages/browser-rum-slim/src/salesforce/README.md b/packages/browser-rum-slim/src/salesforce/README.md index b54e423743..72de801035 100644 --- a/packages/browser-rum-slim/src/salesforce/README.md +++ b/packages/browser-rum-slim/src/salesforce/README.md @@ -6,9 +6,9 @@ This entrypoint is intended for Salesforce Lightning and LWC applications. ### 1. Add the static resource -Copy `packages/browser-rum-salesforce/bundle/datadog-rum-salesforce.js` into your Salesforce project as `staticresources/datadog_rum_salesforce.js`. +Copy `packages/browser-rum-slim/bundle/datadog-rum-slim.js` into your Salesforce project as `staticresources/datadog_rum_slim.js`. -Create the accompanying metadata file `staticresources/datadog_rum_salesforce.resource-meta.xml`: +Create the accompanying metadata file `staticresources/datadog_rum_slim.resource-meta.xml`: ```xml @@ -47,7 +47,7 @@ Create a new LWC component under `lwc/datadogInit/`, for example `lwc/datadogIni ```js import { LightningElement, api, wire } from 'lwc' import { NavigationMixin, CurrentPageReference } from 'lightning/navigation' -import datadogRumSalesforce from '@salesforce/resourceUrl/datadog_rum_salesforce' +import datadogRumSlim from '@salesforce/resourceUrl/datadog_rum_slim' import { loadScript } from 'lightning/platformResourceLoader' let datadogInitialization @@ -98,7 +98,7 @@ export default class DatadogInit extends NavigationMixin(LightningElement) { } loadDatadogRum() { - return loadScript(this, datadogRumSalesforce).then(() => { + return loadScript(this, datadogRumSlim).then(() => { const initConfig = { applicationId: this.applicationId, clientToken: this.clientToken, @@ -122,6 +122,19 @@ export default class DatadogInit extends NavigationMixin(LightningElement) { } ``` +Also create the required component metadata file `lwc/datadogInit/datadogInit.js-meta.xml` to expose it to the utility bar: + +```xml + + + 59.0 + true + + lightning__UtilityBar + + +``` + ### 4. Add the component to a Lightning App Add `datadogInit` to your app's utility bar via a flexipage metadata file. Set `eager` to `true` so the component initializes immediately on page load. From ecbb8550f82df9f84336def594b31a27373a617b Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Mon, 8 Jun 2026 12:18:45 +0200 Subject: [PATCH 05/15] Add unit tests --- .../src/browser/addEventListener.spec.ts | 16 +++++++++++- .../src/browser/cookieAccess.spec.ts | 25 +++++++++++++++++++ .../domain/report/reportObservable.spec.ts | 9 +++++++ .../browser/locationChangeObservable.spec.ts | 16 +++++++++++- .../src/browser/locationChangeObservable.ts | 9 +++++-- 5 files changed, 71 insertions(+), 4 deletions(-) diff --git a/packages/browser-core/src/browser/addEventListener.spec.ts b/packages/browser-core/src/browser/addEventListener.spec.ts index dc59626848..effac45a25 100644 --- a/packages/browser-core/src/browser/addEventListener.spec.ts +++ b/packages/browser-core/src/browser/addEventListener.spec.ts @@ -2,7 +2,7 @@ import type { Configuration } from '../domain/configuration' import { createNewEvent, mockZoneJs, registerCleanupTask } from '../../test' import type { MockZoneJs } from '../../test' import { noop } from '../tools/utils/functionUtils' -import { addEventListener, DOM_EVENT } from './addEventListener' +import { addEventListener, DOM_EVENT, isEventSupported } from './addEventListener' describe('addEventListener', () => { let configuration: Configuration @@ -136,4 +136,18 @@ describe('addEventListener', () => { expect(listener).toHaveBeenCalled() }) }) + + describe('isEventSupported', () => { + it('should return true if the event is supported', () => { + expect(isEventSupported(document, DOM_EVENT.CLICK)).toBe(true) + }) + + it('should return false if the event listener cannot be added', () => { + const eventTarget = { + addEventListener: jasmine.createSpy().and.throwError('unsupported'), + } as unknown as HTMLElement + + expect(isEventSupported(eventTarget, DOM_EVENT.CLICK)).toBe(false) + }) + }) }) diff --git a/packages/browser-core/src/browser/cookieAccess.spec.ts b/packages/browser-core/src/browser/cookieAccess.spec.ts index b20c379265..646806c523 100644 --- a/packages/browser-core/src/browser/cookieAccess.spec.ts +++ b/packages/browser-core/src/browser/cookieAccess.spec.ts @@ -12,6 +12,7 @@ import { areCookiesAuthorized, createCookieStoreAccess, createDocumentCookieAccess, + isCookieStoreSupported, WATCH_COOKIE_INTERVAL_DELAY, } from './cookieAccess' @@ -315,4 +316,28 @@ describe('cookieAccess', () => { expect(result).toBe(false) }) }) + + describe('isCookieStoreSupported', () => { + it('returns false when the CookieStore API is not available', () => { + disableCookieStore() + + expect(isCookieStoreSupported()).toBe(false) + }) + + it('returns true when the CookieStore API supports change events', () => { + const cookieStore = new EventTarget() + replaceMockable(globalObject.cookieStore, cookieStore as unknown as typeof globalObject.cookieStore) + + expect(isCookieStoreSupported()).toBe(true) + }) + + it('returns false when the CookieStore API does not support change events', () => { + const cookieStore = { + addEventListener: jasmine.createSpy().and.throwError('unsupported'), + } + replaceMockable(globalObject.cookieStore, cookieStore as unknown as typeof globalObject.cookieStore) + + expect(isCookieStoreSupported()).toBe(false) + }) + }) }) diff --git a/packages/browser-core/src/domain/report/reportObservable.spec.ts b/packages/browser-core/src/domain/report/reportObservable.spec.ts index 2d8c4ecd6b..d3919e4ce8 100644 --- a/packages/browser-core/src/domain/report/reportObservable.spec.ts +++ b/packages/browser-core/src/domain/report/reportObservable.spec.ts @@ -68,4 +68,13 @@ describe('report observable', () => { csp: { disposition: 'enforce' }, }) }) + + it(`should not notify ${RawReportType.cspViolation} when the event is not supported`, () => { + ;(EventTarget.prototype.addEventListener as jasmine.Spy).and.throwError('unsupported') + + consoleSubscription = initReportObservable(configuration, [RawReportType.cspViolation]).subscribe(notifyReport) + cspEventListener.dispatchEvent() + + expect(notifyReport).not.toHaveBeenCalled() + }) }) diff --git a/packages/browser-rum-core/src/browser/locationChangeObservable.spec.ts b/packages/browser-rum-core/src/browser/locationChangeObservable.spec.ts index 412370815c..50df468211 100644 --- a/packages/browser-rum-core/src/browser/locationChangeObservable.spec.ts +++ b/packages/browser-rum-core/src/browser/locationChangeObservable.spec.ts @@ -1,4 +1,5 @@ -import { registerCleanupTask } from '@datadog/browser-core/test' +import { globalObject } from '@datadog/browser-core' +import { buildLocation, registerCleanupTask, replaceMockable } from '@datadog/browser-core/test' import type { RumConfiguration } from '@datadog/browser-rum-core' import { createLocationChangeObservable } from './locationChangeObservable' @@ -37,6 +38,19 @@ describe('locationChangeObservable', () => { expect(observer).not.toHaveBeenCalled() }) + it('should use globalObject.location to detect location changes', () => { + const fakeLocation = buildLocation('/fake-location') + replaceMockable(globalObject.location, fakeLocation) + const observer = setup() + + Object.assign(fakeLocation, buildLocation('/fake-location?bar=qux')) + history.pushState({}, '', '/foo?bar=qux') + + const locationChanges = observer.calls.argsFor(0)[0] + expect(locationChanges.oldLocation.href).toMatch(/\/fake-location$/) + expect(locationChanges.newLocation.href).toMatch(/\/fake-location\?bar=qux$/) + }) + it('allow frameworks to patch history.pushState', () => { const wrapperSpy = setupHistoryInstancePushStateWrapper() const observer = setup() diff --git a/packages/browser-rum-core/src/browser/locationChangeObservable.ts b/packages/browser-rum-core/src/browser/locationChangeObservable.ts index 9ec1b56953..e3e756f83d 100644 --- a/packages/browser-rum-core/src/browser/locationChangeObservable.ts +++ b/packages/browser-rum-core/src/browser/locationChangeObservable.ts @@ -3,6 +3,7 @@ import { DOM_EVENT, globalObject, instrumentMethod, + mockable, Observable, shallowClone, } from '@datadog/browser-core' @@ -14,7 +15,7 @@ export interface LocationChange { } export function createLocationChangeObservable(configuration: RumConfiguration) { - let currentLocation = shallowClone(globalObject.location) + let currentLocation = shallowClone(getLocation()) return new Observable((observable) => { const { stop: stopHistoryTracking } = trackHistory(configuration, onLocationChange) @@ -24,7 +25,7 @@ export function createLocationChangeObservable(configuration: RumConfiguration) if (currentLocation.href === location.href) { return } - const newLocation = shallowClone(location) + const newLocation = shallowClone(getLocation()) observable.notify({ newLocation, oldLocation: currentLocation, @@ -39,6 +40,10 @@ export function createLocationChangeObservable(configuration: RumConfiguration) }) } +function getLocation() { + return mockable(globalObject.location) +} + function trackHistory(configuration: RumConfiguration, onHistoryChange: () => void) { const { stop: stopInstrumentingPushState } = instrumentMethod( getHistoryInstrumentationTarget('pushState'), From d9b95ba390e15a3d04e960269e36ee68070bb40e Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Tue, 9 Jun 2026 10:05:12 +0200 Subject: [PATCH 06/15] Address location change observable comments --- .../browser-rum-core/src/browser/locationChangeObservable.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/browser-rum-core/src/browser/locationChangeObservable.ts b/packages/browser-rum-core/src/browser/locationChangeObservable.ts index e3e756f83d..8083286853 100644 --- a/packages/browser-rum-core/src/browser/locationChangeObservable.ts +++ b/packages/browser-rum-core/src/browser/locationChangeObservable.ts @@ -22,7 +22,7 @@ export function createLocationChangeObservable(configuration: RumConfiguration) const { stop: stopHashTracking } = trackHash(configuration, onLocationChange) function onLocationChange() { - if (currentLocation.href === location.href) { + if (currentLocation.href === getLocation().href) { return } const newLocation = shallowClone(getLocation()) From a55b33fe18188c26c4a43c6c0fdd4d1c8ae8af03 Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Tue, 9 Jun 2026 15:53:40 +0200 Subject: [PATCH 07/15] Make window optional in globalObject --- packages/browser-core/src/browser/addEventListener.ts | 6 +++++- packages/browser-core/src/tools/globalObject.ts | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/browser-core/src/browser/addEventListener.ts b/packages/browser-core/src/browser/addEventListener.ts index bc79108c38..0030332a3d 100644 --- a/packages/browser-core/src/browser/addEventListener.ts +++ b/packages/browser-core/src/browser/addEventListener.ts @@ -150,9 +150,13 @@ export function addEventListeners & string>( - eventTarget: Target, + eventTarget: Target | undefined, eventName: EventName ) { + if (!eventTarget) { + return false + } + try { addEventListener({}, eventTarget, eventName, noop).stop() return true diff --git a/packages/browser-core/src/tools/globalObject.ts b/packages/browser-core/src/tools/globalObject.ts index 94524e14a5..6b8d49affd 100644 --- a/packages/browser-core/src/tools/globalObject.ts +++ b/packages/browser-core/src/tools/globalObject.ts @@ -12,7 +12,7 @@ import type { BrowserNavigator, CookieStore, ProfilerConstructor } from '../brow * Feel free to add more properties as needed, or mark some properties as optional when they are. */ // eslint-disable-next-line no-restricted-syntax -export interface GlobalObject extends Omit { +export interface GlobalObject extends Omit { navigator: BrowserNavigator // cookieStore is not available in all browsers yet @@ -23,6 +23,9 @@ export interface GlobalObject extends Omit Date: Tue, 9 Jun 2026 17:56:35 +0200 Subject: [PATCH 08/15] Fix format and codex reviews --- .../browser-core/src/tools/globalObject.ts | 5 ++++- .../browser-rum-slim/src/salesforce/README.md | 19 ++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/browser-core/src/tools/globalObject.ts b/packages/browser-core/src/tools/globalObject.ts index 6b8d49affd..f53c72f7ea 100644 --- a/packages/browser-core/src/tools/globalObject.ts +++ b/packages/browser-core/src/tools/globalObject.ts @@ -12,7 +12,10 @@ import type { BrowserNavigator, CookieStore, ProfilerConstructor } from '../brow * Feel free to add more properties as needed, or mark some properties as optional when they are. */ // eslint-disable-next-line no-restricted-syntax -export interface GlobalObject extends Omit { +export interface GlobalObject extends Omit< + typeof globalThis, + 'queueMicrotask' | 'cookieStore' | 'Profiler' | 'window' +> { navigator: BrowserNavigator // cookieStore is not available in all browsers yet diff --git a/packages/browser-rum-slim/src/salesforce/README.md b/packages/browser-rum-slim/src/salesforce/README.md index 72de801035..b275e55790 100644 --- a/packages/browser-rum-slim/src/salesforce/README.md +++ b/packages/browser-rum-slim/src/salesforce/README.md @@ -42,7 +42,13 @@ Create a CSP trusted site metadata file under `cspTrustedSites/`, for example `c ### 3. Create the LWC component -Create a new LWC component under `lwc/datadogInit/`, for example `lwc/datadogInit/datadogInit.js`: +Create a new LWC component under `lwc/datadogInit/`. A LWC bundle requires an HTML template, so first create `lwc/datadogInit/datadogInit.html`: + +```html + +``` + +Then create `lwc/datadogInit/datadogInit.js`: ```js import { LightningElement, api, wire } from 'lwc' @@ -59,8 +65,6 @@ export default class DatadogInit extends NavigationMixin(LightningElement) { @api site @api service @api env - @api allowedTracingUrls - @api trackViewsManually connectedCallback() { this.initialize() @@ -132,6 +136,15 @@ Also create the required component metadata file `lwc/datadogInit/datadogInit.js lightning__UtilityBar + + + + + + + + + ``` From aa6f78408bf7fd6a51f800afb4056cefee29ef4f Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Tue, 9 Jun 2026 18:15:25 +0200 Subject: [PATCH 09/15] Fix linter --- packages/browser-core/src/tools/globalObject.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/browser-core/src/tools/globalObject.ts b/packages/browser-core/src/tools/globalObject.ts index f53c72f7ea..dd7e38f9cf 100644 --- a/packages/browser-core/src/tools/globalObject.ts +++ b/packages/browser-core/src/tools/globalObject.ts @@ -11,8 +11,8 @@ import type { BrowserNavigator, CookieStore, ProfilerConstructor } from '../brow * * Feel free to add more properties as needed, or mark some properties as optional when they are. */ -// eslint-disable-next-line no-restricted-syntax export interface GlobalObject extends Omit< + // eslint-disable-next-line no-restricted-syntax typeof globalThis, 'queueMicrotask' | 'cookieStore' | 'Profiler' | 'window' > { From b26089650ce5965084b44450b9b09784712cf13a Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Wed, 10 Jun 2026 11:24:46 +0200 Subject: [PATCH 10/15] Initialize sf-lwc-app test app with eslint and prettier ignored --- .prettierignore | 1 + eslint.config.ts | 1 + test/apps/sf-lwc-app/.gitignore | 3 + test/apps/sf-lwc-app/README.md | 24 +++++ .../config/project-scratch-def.json | 10 +++ test/apps/sf-lwc-app/package.json | 9 ++ test/apps/sf-lwc-app/scripts/setup.mjs | 88 +++++++++++++++++++ test/apps/sf-lwc-app/sfdx-project.json | 10 +++ 8 files changed, 146 insertions(+) create mode 100644 test/apps/sf-lwc-app/.gitignore create mode 100644 test/apps/sf-lwc-app/README.md create mode 100644 test/apps/sf-lwc-app/config/project-scratch-def.json create mode 100644 test/apps/sf-lwc-app/package.json create mode 100644 test/apps/sf-lwc-app/scripts/setup.mjs create mode 100644 test/apps/sf-lwc-app/sfdx-project.json diff --git a/.prettierignore b/.prettierignore index 0dc60f5c7c..5e8679b302 100644 --- a/.prettierignore +++ b/.prettierignore @@ -13,6 +13,7 @@ yarn.lock /generated-docs /developer-extension/.output /developer-extension/.wxt +/test/apps/sf-lwc-app/* # Symlinks (prettier would otherwise resolve and overwrite the target file or replace the symlink with a regular file) /packages/browser-rum-slim/BROWSER_SUPPORT.md diff --git a/eslint.config.ts b/eslint.config.ts index 1d4f4e4c3f..0cfec0738d 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -47,6 +47,7 @@ export default defineConfig( 'test/apps/angular-app', 'test/apps/vue-router-app', 'test/apps/nuxt-app', + 'test/apps/sf-lwc-app', 'sandbox', 'coverage', 'rum-events-format', diff --git a/test/apps/sf-lwc-app/.gitignore b/test/apps/sf-lwc-app/.gitignore new file mode 100644 index 0000000000..a8488dce86 --- /dev/null +++ b/test/apps/sf-lwc-app/.gitignore @@ -0,0 +1,3 @@ +.sf +.sfdx +/force-app/main/default/staticresources/datadog_rum_slim.js \ No newline at end of file diff --git a/test/apps/sf-lwc-app/README.md b/test/apps/sf-lwc-app/README.md new file mode 100644 index 0000000000..6a37caa304 --- /dev/null +++ b/test/apps/sf-lwc-app/README.md @@ -0,0 +1,24 @@ +# SF LWC App + +Salesforce DX project used to exercise browser-sdk behavior in a real Lightning Web Components runtime. + +This app is Lightning-only. + +## What It Contains + +- A Lightning app named `SF LWC App` +- A trimmed Home page with Datadog test controls +- A `Product Explorer` app page with three hardcoded editable products +- `c:datadogInit` in the utility bar, backed by the `datadog_rum_slim` static resource + +## Deploy + +From this directory: + +```sh +npm run setup -- -o engrumdev --ignore-conflicts +``` + +The setup script copies the local RUM slim bundle into the static resource, deploys the app, assigns the app permission set to the target user, and prints the Home URL. + +If prompted for a user. Log into 1Password and use `beltran.bulbarella@datadoghq.com.engrumdev` diff --git a/test/apps/sf-lwc-app/config/project-scratch-def.json b/test/apps/sf-lwc-app/config/project-scratch-def.json new file mode 100644 index 0000000000..09a39a14e0 --- /dev/null +++ b/test/apps/sf-lwc-app/config/project-scratch-def.json @@ -0,0 +1,10 @@ +{ + "orgName": "SF LWC App", + "edition": "Developer", + "features": ["EnableSetPasswordInApi"], + "settings": { + "lightningExperienceSettings": { + "enableS1DesktopEnabled": true + } + } +} diff --git a/test/apps/sf-lwc-app/package.json b/test/apps/sf-lwc-app/package.json new file mode 100644 index 0000000000..9576877900 --- /dev/null +++ b/test/apps/sf-lwc-app/package.json @@ -0,0 +1,9 @@ +{ + "name": "sf-lwc-app", + "private": true, + "version": "1.0.0", + "description": "Salesforce Lightning app for browser-sdk Salesforce testing", + "scripts": { + "setup": "node scripts/setup.mjs" + } +} diff --git a/test/apps/sf-lwc-app/scripts/setup.mjs b/test/apps/sf-lwc-app/scripts/setup.mjs new file mode 100644 index 0000000000..d48ca63a3b --- /dev/null +++ b/test/apps/sf-lwc-app/scripts/setup.mjs @@ -0,0 +1,88 @@ +import { copyFileSync, existsSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { spawnSync } from 'node:child_process' +import { fileURLToPath } from 'node:url' + +const args = process.argv.slice(2) +const orgArgs = getOrgArgs(args) + +syncDatadogBundle() +run('sf', ['project', 'deploy', 'start', ...args]) +assignPermissionSet() +printAppUrl() + +// Copies the Datadog RUM slim bundle to the app's staticresource +function syncDatadogBundle() { + const appDir = resolve(dirname(fileURLToPath(import.meta.url)), '..') + const browserSdkDir = resolve(appDir, '..', '..', '..') + const sourceBundle = resolve(browserSdkDir, 'packages/browser-rum-slim/bundle/datadog-rum-slim.js') + const targetBundle = resolve(appDir, 'force-app/main/default/staticresources/datadog_rum_slim.js') + + if (!existsSync(sourceBundle)) { + throw new Error( + `Missing Datadog RUM slim bundle at ${sourceBundle}. Run from a browser-sdk checkout with the bundle built.` + ) + } + + copyFileSync(sourceBundle, targetBundle) + console.log(`Synced ${targetBundle}`) +} + +function run(command, args) { + const result = spawnSync(command, args, { stdio: 'inherit' }) + if (result.status !== 0) { + process.exit(result.status ?? 1) + } +} + +function assignPermissionSet() { + const result = spawnSync('sf', ['org', 'assign', 'permset', '-n', 'SF_LWC_App', ...orgArgs], { + encoding: 'utf8', + }) + + if (result.status === 0) { + console.log('Assigned SF_LWC_App permission set') + return + } + + const output = `${result.stdout}\n${result.stderr}` + if (output.includes('Duplicate PermissionSetAssignment')) { + console.log('SF_LWC_App permission set already assigned') + return + } + + process.stdout.write(result.stdout) + process.stderr.write(result.stderr) + process.exit(result.status ?? 1) +} + +function printAppUrl() { + const result = spawnSync('sf', ['org', 'open', '-p', '/lightning/page/home', '--url-only', '--json', ...orgArgs], { + encoding: 'utf8', + }) + + if (result.status !== 0) { + process.stderr.write(result.stderr) + process.exit(result.status ?? 1) + } + + const { result: openResult } = JSON.parse(result.stdout) + console.log(`Home: ${openResult.url}`) +} + +function getOrgArgs(args) { + const orgArgs = [] + + for (let index = 0; index < args.length; index += 1) { + const arg = args[index] + + if ((arg === '-o' || arg === '--target-org') && args[index + 1]) { + orgArgs.push(arg, args[index + 1]) + index += 1 + } else if (arg.startsWith('--target-org=')) { + orgArgs.push(arg) + } + } + + return orgArgs +} diff --git a/test/apps/sf-lwc-app/sfdx-project.json b/test/apps/sf-lwc-app/sfdx-project.json new file mode 100644 index 0000000000..a68e6367f3 --- /dev/null +++ b/test/apps/sf-lwc-app/sfdx-project.json @@ -0,0 +1,10 @@ +{ + "packageDirectories": [ + { + "path": "force-app", + "default": true + } + ], + "namespace": "", + "sourceApiVersion": "65.0" +} From a7421450f12572908d1b1326816a4e46ac176c21 Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Wed, 10 Jun 2026 11:25:10 +0200 Subject: [PATCH 11/15] Add minimal LWC app for testing (Navigation, views, actions, resources, etc.) --- .../applications/SF_LWC_App.app-meta.xml | 24 +++ ...take_datadoghq_com.cspTrustedSite-meta.xml | 15 ++ .../dummyjson_com.cspTrustedSite-meta.xml | 15 ++ .../Product_Explorer.flexipage-meta.xml | 18 ++ .../SF_LWC_App_Home.flexipage-meta.xml | 18 ++ .../SF_LWC_App_UtilityBar.flexipage-meta.xml | 76 ++++++++ .../customActionButtons.css | 49 +++++ .../customActionButtons.html | 179 ++++++++++++++++++ .../customActionButtons.js | 123 ++++++++++++ .../customActionButtons.js-meta.xml | 9 + .../default/lwc/datadogInit/datadogInit.html | 1 + .../default/lwc/datadogInit/datadogInit.js | 75 ++++++++ .../lwc/datadogInit/datadogInit.js-meta.xml | 20 ++ .../force-app/main/default/lwc/jsconfig.json | 13 ++ .../lwc/productExplorer/productExplorer.css | 86 +++++++++ .../lwc/productExplorer/productExplorer.html | 148 +++++++++++++++ .../lwc/productExplorer/productExplorer.js | 90 +++++++++ .../productExplorer.js-meta.xml | 9 + .../SF_LWC_App.permissionset-meta.xml | 13 ++ .../datadog_rum_slim.resource-meta.xml | 5 + .../tabs/Product_Explorer.tab-meta.xml | 7 + 21 files changed, 993 insertions(+) create mode 100644 test/apps/sf-lwc-app/force-app/main/default/applications/SF_LWC_App.app-meta.xml create mode 100644 test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/browser_intake_datadoghq_com.cspTrustedSite-meta.xml create mode 100644 test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/dummyjson_com.cspTrustedSite-meta.xml create mode 100644 test/apps/sf-lwc-app/force-app/main/default/flexipages/Product_Explorer.flexipage-meta.xml create mode 100644 test/apps/sf-lwc-app/force-app/main/default/flexipages/SF_LWC_App_Home.flexipage-meta.xml create mode 100644 test/apps/sf-lwc-app/force-app/main/default/flexipages/SF_LWC_App_UtilityBar.flexipage-meta.xml create mode 100644 test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.css create mode 100644 test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.html create mode 100644 test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.js create mode 100644 test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.js-meta.xml create mode 100644 test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.html create mode 100644 test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.js create mode 100644 test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.js-meta.xml create mode 100644 test/apps/sf-lwc-app/force-app/main/default/lwc/jsconfig.json create mode 100644 test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.css create mode 100644 test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.html create mode 100644 test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.js create mode 100644 test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.js-meta.xml create mode 100644 test/apps/sf-lwc-app/force-app/main/default/permissionsets/SF_LWC_App.permissionset-meta.xml create mode 100644 test/apps/sf-lwc-app/force-app/main/default/staticresources/datadog_rum_slim.resource-meta.xml create mode 100644 test/apps/sf-lwc-app/force-app/main/default/tabs/Product_Explorer.tab-meta.xml diff --git a/test/apps/sf-lwc-app/force-app/main/default/applications/SF_LWC_App.app-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/applications/SF_LWC_App.app-meta.xml new file mode 100644 index 0000000000..0ba716cab3 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/applications/SF_LWC_App.app-meta.xml @@ -0,0 +1,24 @@ + + + + Tab + SF_LWC_App_Home + Large + false + Flexipage + standard-home + + Small + Large + false + false + false + false + + Standard + all + standard-home + Product_Explorer + Lightning + SF_LWC_App_UtilityBar + diff --git a/test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/browser_intake_datadoghq_com.cspTrustedSite-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/browser_intake_datadoghq_com.cspTrustedSite-meta.xml new file mode 100644 index 0000000000..ecf051c561 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/browser_intake_datadoghq_com.cspTrustedSite-meta.xml @@ -0,0 +1,15 @@ + + + false + false + All + Datadog browser RUM intake for US1 + https://browser-intake-datadoghq.com + true + true + false + false + false + false + false + diff --git a/test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/dummyjson_com.cspTrustedSite-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/dummyjson_com.cspTrustedSite-meta.xml new file mode 100644 index 0000000000..c874db7bab --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/dummyjson_com.cspTrustedSite-meta.xml @@ -0,0 +1,15 @@ + + + false + false + All + Dummy JSON API for Datadog APM correlation testing + https://dummyjson.com + true + true + false + false + false + false + false + diff --git a/test/apps/sf-lwc-app/force-app/main/default/flexipages/Product_Explorer.flexipage-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/flexipages/Product_Explorer.flexipage-meta.xml new file mode 100644 index 0000000000..142b4f2274 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/flexipages/Product_Explorer.flexipage-meta.xml @@ -0,0 +1,18 @@ + + + + + + productExplorer + productExplorer + + + main + Region + + Product Explorer + + AppPage + diff --git a/test/apps/sf-lwc-app/force-app/main/default/flexipages/SF_LWC_App_Home.flexipage-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/flexipages/SF_LWC_App_Home.flexipage-meta.xml new file mode 100644 index 0000000000..109a44e956 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/flexipages/SF_LWC_App_Home.flexipage-meta.xml @@ -0,0 +1,18 @@ + + + + + + customActionButtons + customActionButtons + + + top + Region + + SF LWC App Home + + HomePage + diff --git a/test/apps/sf-lwc-app/force-app/main/default/flexipages/SF_LWC_App_UtilityBar.flexipage-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/flexipages/SF_LWC_App_UtilityBar.flexipage-meta.xml new file mode 100644 index 0000000000..a933d40747 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/flexipages/SF_LWC_App_UtilityBar.flexipage-meta.xml @@ -0,0 +1,76 @@ + + + + + + + applicationId + 1397744d-34f4-4a6a-a735-801e31c18221 + + + clientToken + pub2ad3fe2578f01b9f329bd0ea4a2f08c5 + + + site + datadoghq.com + + + service + browser-sdk-sandbox + + + env + dev + + + eager + decorator + true + + + height + decorator + 240 + + + icon + decorator + metrics + + + label + decorator + Datadog Init + + + scrollable + decorator + false + + + width + decorator + 320 + + datadogInit + datadogInit + + + utilityItems + Region + + + backgroundComponents + Background + + SF LWC App UtilityBar + + UtilityBar + diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.css b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.css new file mode 100644 index 0000000000..d3b9b307e9 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.css @@ -0,0 +1,49 @@ +.action-card { + background: #ffffff; + border: 1px solid #d8dde6; + border-radius: 0.75rem; + box-shadow: 0 6px 18px rgba(24, 24, 24, 0.08); + margin: 1.5rem auto; + max-width: 48rem; + padding: 1.5rem; +} + +.action-card__header h2 { + color: #032d60; + font-size: 1.25rem; + margin: 0 0 0.25rem; +} + +.action-card__header p { + color: #5c5c5c; + margin: 0; +} + +.action-card__buttons { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin-top: 1rem; +} + +.action-card__status { + color: #032d60; + font-size: 0.875rem; + margin-top: 1rem; +} + +.selector-probe { + background: #0176d3; + border: 1px solid #0176d3; + border-radius: 0.25rem; + color: #ffffff; + cursor: pointer; + font: inherit; + min-height: 2rem; + padding: 0 1rem; +} + +.selector-probe:hover { + background: #014486; + border-color: #014486; +} diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.html b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.html new file mode 100644 index 0000000000..c75deaaaca --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.html @@ -0,0 +1,179 @@ + diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.js b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.js new file mode 100644 index 0000000000..5f1a3e4a7d --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.js @@ -0,0 +1,123 @@ +import { LightningElement, track } from 'lwc' + +export default class CustomActionButtons extends LightningElement { + @track lastActionName + @track lastErrorName + @track lastResourceName + @track lastSelectorProbe + + handleActionClick(event) { + const actionName = event.currentTarget.dataset.actionName + this.lastActionName = actionName + + window.DD_RUM?.addAction(actionName, { + source: 'salesforce-home-button', + pathname: window.location?.pathname, + }) + } + + handleErrorClick(event) { + const errorName = event.currentTarget.dataset.errorName + this.lastErrorName = errorName + + window.DD_RUM?.addError(new Error(errorName), { + source: 'salesforce-home-button', + pathname: window.location?.pathname, + }) + } + + handleRuntimeErrorClick() { + throw new Error('salesforce direct runtime error test') + } + + handleLongTaskClick() { + window.setTimeout(() => { + const start = performance.now() + while (performance.now() - start < 750) { + /* spin */ + } + }, 0) + } + + // Vitals: report a completed duration vital directly. + handleAddDurationVitalClick() { + window.DD_RUM?.addDurationVital('salesforce.duration_vital_test', { + startTime: Date.now() - 250, + duration: 250, + description: 'salesforce duration vital test', + context: { + source: 'salesforce-home-button', + }, + }) + } + + // Vitals: measure a live 300ms window. + handleStartStopVitalClick() { + const vital = window.DD_RUM?.startDurationVital('salesforce.start_stop_vital_test', { + description: 'salesforce start-stop vital test', + context: { + source: 'salesforce-home-button', + }, + }) + + setTimeout(() => window.DD_RUM?.stopDurationVital(vital), 300) + } + + async handleFetchResourceClick() { + const token = `dd-fetch-test-${Date.now()}` + const url = this.getResourceTestUrl(token) + this.lastResourceName = `fetch: ${token}` + + try { + const response = await window.fetch(url, { cache: 'no-store' }) + await response.text() + } catch (error) { + window.DD_RUM?.addError(error, { + source: 'salesforce-resource-button', + resourceType: 'fetch', + url, + }) + } + } + + handleXhrResourceClick() { + const token = `dd-xhr-test-${Date.now()}` + const url = this.getResourceTestUrl(token) + this.lastResourceName = `xhr: ${token}` + + const xhr = new window.XMLHttpRequest() + xhr.open('GET', url) + xhr.onerror = () => { + window.DD_RUM?.addError(new Error('salesforce xhr resource test failed'), { + source: 'salesforce-resource-button', + resourceType: 'xhr', + url, + }) + } + xhr.send() + } + + handleSelectorProbeClick(event) { + const innerActionName = event.currentTarget.getAttribute('data-dd-action-name') + this.lastSelectorProbe = innerActionName + + window.DD_RUM?.addAction('selector probe internal target', { + source: 'salesforce-selector-probe', + currentTargetActionName: innerActionName, + currentTargetTagName: event.currentTarget.tagName, + targetTagName: event.target.tagName, + composedPath: this.getComposedPathNames(event), + }) + } + + getResourceTestUrl(token) { + return `https://dummyjson.com/products/1?${token}` + } + + getComposedPathNames(event) { + return event + .composedPath() + .slice(0, 6) + .map((target) => target.tagName || target.nodeName || String(target)) + } +} diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.js-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.js-meta.xml new file mode 100644 index 0000000000..a183d52180 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.js-meta.xml @@ -0,0 +1,9 @@ + + + 65.0 + true + Custom Action Buttons + + lightning__HomePage + + diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.html b/test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.html new file mode 100644 index 0000000000..cc340bc4c9 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.html @@ -0,0 +1 @@ + diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.js b/test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.js new file mode 100644 index 0000000000..dd1562286f --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.js @@ -0,0 +1,75 @@ +import { LightningElement, api, wire } from 'lwc' +import { NavigationMixin, CurrentPageReference } from 'lightning/navigation' +import datadogRumSlim from '@salesforce/resourceUrl/datadog_rum_slim' +import { loadScript } from 'lightning/platformResourceLoader' + +let datadogInitialization +let lastStartedUrl + +export default class DatadogInit extends NavigationMixin(LightningElement) { + @api applicationId = '1397744d-34f4-4a6a-a735-801e31c18221' + @api clientToken = 'pub2ad3fe2578f01b9f329bd0ea4a2f08c5' + @api site = 'datadoghq.com' + @api service = 'my-salesforce-app' + @api env = 'dev' + @api allowedTracingUrls + @api trackViewsManually + + connectedCallback() { + this.initialize() + } + + @wire(CurrentPageReference) + handleCurrentPageReference(pageReference) { + if (!pageReference) { + return + } + + this.initialize() + + if (window.DD_RUM) { + this.startViewForPageReference(pageReference) + } + } + + startViewForPageReference(pageReference) { + const urlPromise = this[NavigationMixin.GenerateUrl](pageReference) + urlPromise.then((url) => { + if (url === lastStartedUrl) { + return + } + lastStartedUrl = url + const absoluteUrl = new URL(url, window.location.origin).href + window.DD_RUM.startView({ name: url, url: absoluteUrl }) + }) + } + + initialize() { + if (!datadogInitialization) { + datadogInitialization = this.loadDatadogRum() + } + } + + loadDatadogRum() { + return loadScript(this, datadogRumSlim).then(() => { + const initConfig = { + applicationId: this.applicationId, + clientToken: this.clientToken, + env: this.env, + service: this.service, + site: this.site, + trackViewsManually: true, + trackEarlyRequests: true, + trackLongTasks: true, + trackResources: true, + trackUserInteractions: true, + } + window.DD_RUM.init(initConfig) + lastStartedUrl = window.location.pathname + window.location.search + window.location.hash + window.DD_RUM.startView({ + name: lastStartedUrl, + url: window.location.href, + }) + }) + } +} diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.js-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.js-meta.xml new file mode 100644 index 0000000000..61460d3979 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/datadogInit/datadogInit.js-meta.xml @@ -0,0 +1,20 @@ + + + 64.0 + true + Datadog Init + + lightning__UtilityBar + + + + + + + + + + + + + diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/jsconfig.json b/test/apps/sf-lwc-app/force-app/main/default/lwc/jsconfig.json new file mode 100644 index 0000000000..febe5fd90e --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/jsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "baseUrl": ".", + "paths": { + "c/*": ["*"] + } + }, + "include": ["**/*", "../../../../.sfdx/typings/lwc/**/*.d.ts"], + "typeAcquisition": { + "include": ["jest"] + } +} diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.css b/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.css new file mode 100644 index 0000000000..47363fbc8c --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.css @@ -0,0 +1,86 @@ +.explorer { + display: grid; + gap: 1rem; + grid-template-columns: minmax(14rem, 18rem) minmax(0, 1fr); + padding: 1.5rem; +} + +.product-list { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.product-button { + background: #ffffff; + border: 1px solid #d8dde6; + border-radius: 0.5rem; + color: #181818; + cursor: pointer; + display: grid; + gap: 0.25rem; + padding: 1rem; + text-align: left; +} + +.product-button:hover, +.product-button_selected { + border-color: #0176d3; + box-shadow: 0 0 0 1px #0176d3 inset; +} + +.product-name { + color: #032d60; + font-size: 1rem; + font-weight: 700; +} + +.product-meta, +.product-price { + color: #5c5c5c; + font-size: 0.875rem; +} + +.editor { + background: #ffffff; + border: 1px solid #d8dde6; + border-radius: 0.5rem; + padding: 1rem; +} + +.editor-header { + align-items: flex-start; + display: flex; + justify-content: space-between; + gap: 1rem; + margin-bottom: 1rem; +} + +.editor-header h2 { + color: #032d60; + font-size: 1.25rem; + margin: 0 0 0.25rem; +} + +.editor-header p { + color: #5c5c5c; + margin: 0; +} + +.editor-grid { + display: grid; + gap: 0.75rem; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +lightning-textarea { + display: block; + margin-top: 0.75rem; +} + +@media (max-width: 48rem) { + .explorer, + .editor-grid { + grid-template-columns: 1fr; + } +} diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.html b/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.html new file mode 100644 index 0000000000..800e60d860 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.html @@ -0,0 +1,148 @@ + diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.js b/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.js new file mode 100644 index 0000000000..4f66655b99 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.js @@ -0,0 +1,90 @@ +import { LightningElement } from 'lwc' + +const INITIAL_PRODUCTS = [ + { + id: 'dynamo-x2', + name: 'Dynamo X2', + category: 'Mountain', + level: 'Expert', + material: 'Carbon', + msrp: 7200, + motor: 'High torque 252 watt', + battery: '702Wh', + brakes: 'Hydraulic disc', + description: 'A high-output trail bike for technical climbs and fast descents.', + }, + { + id: 'electra-x2', + name: 'Electra X2', + category: 'Road', + level: 'Intermediate', + material: 'Aluminum', + msrp: 4300, + motor: 'Balanced 250 watt', + battery: '502Wh', + brakes: 'Dual-pivot caliper', + description: 'A lightweight commuter with fast handling and comfortable range.', + }, + { + id: 'fuse-x2', + name: 'Fuse X2', + category: 'Commuter', + level: 'Beginner', + material: 'Aluminum', + msrp: 2600, + motor: 'Efficient 250 watt', + battery: '402Wh', + brakes: 'Mechanical disc', + description: 'An approachable city bike for everyday testing workflows.', + }, +] + +export default class ProductExplorer extends LightningElement { + products = cloneProducts(INITIAL_PRODUCTS) + selectedProductId = INITIAL_PRODUCTS[0].id + + categoryOptions = toOptions(['Commuter', 'Mountain', 'Road']) + levelOptions = toOptions(['Beginner', 'Intermediate', 'Expert']) + materialOptions = toOptions(['Aluminum', 'Carbon', 'Steel']) + + get productsForView() { + return this.products.map((product) => ({ + ...product, + buttonClass: product.id === this.selectedProductId ? 'product-button product-button_selected' : 'product-button', + })) + } + + get selectedProduct() { + return this.products.find((product) => product.id === this.selectedProductId) + } + + handleSelectProduct(event) { + this.selectedProductId = event.currentTarget.dataset.id + } + + handleFieldChange(event) { + const field = event.currentTarget.dataset.field + const value = field === 'msrp' ? Number(event.detail.value) : event.detail.value + + this.products = this.products.map((product) => + product.id === this.selectedProductId + ? { + ...product, + [field]: value, + } + : product + ) + } + + handleReset() { + this.products = cloneProducts(INITIAL_PRODUCTS) + } +} + +function cloneProducts(products) { + return products.map((product) => ({ ...product })) +} + +function toOptions(values) { + return values.map((value) => ({ label: value, value })) +} diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.js-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.js-meta.xml new file mode 100644 index 0000000000..27e42c077d --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/productExplorer/productExplorer.js-meta.xml @@ -0,0 +1,9 @@ + + + 65.0 + true + Product Explorer + + lightning__AppPage + + diff --git a/test/apps/sf-lwc-app/force-app/main/default/permissionsets/SF_LWC_App.permissionset-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/permissionsets/SF_LWC_App.permissionset-meta.xml new file mode 100644 index 0000000000..8fa0e908a5 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/permissionsets/SF_LWC_App.permissionset-meta.xml @@ -0,0 +1,13 @@ + + + + SF_LWC_App + true + + false + + + Product_Explorer + Visible + + diff --git a/test/apps/sf-lwc-app/force-app/main/default/staticresources/datadog_rum_slim.resource-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/staticresources/datadog_rum_slim.resource-meta.xml new file mode 100644 index 0000000000..811f7d8899 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/staticresources/datadog_rum_slim.resource-meta.xml @@ -0,0 +1,5 @@ + + + Public + application/javascript + diff --git a/test/apps/sf-lwc-app/force-app/main/default/tabs/Product_Explorer.tab-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/tabs/Product_Explorer.tab-meta.xml new file mode 100644 index 0000000000..e8104b8cc4 --- /dev/null +++ b/test/apps/sf-lwc-app/force-app/main/default/tabs/Product_Explorer.tab-meta.xml @@ -0,0 +1,7 @@ + + + Created by Lightning App Builder + Product_Explorer + + Custom3: Sun + From dfab35506aee170067bdce6c0799f142e5a57859 Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Wed, 10 Jun 2026 11:50:07 +0200 Subject: [PATCH 12/15] fix: build, woke, home url in console output --- test/apps/sf-lwc-app/README.md | 2 +- .../dummyjson_com.cspTrustedSite-meta.xml | 4 ++-- .../lwc/customActionButtons/customActionButtons.js | 2 +- test/apps/sf-lwc-app/package.json | 1 - test/apps/sf-lwc-app/scripts/setup.mjs | 10 +++++++--- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/test/apps/sf-lwc-app/README.md b/test/apps/sf-lwc-app/README.md index 6a37caa304..735bc85ea9 100644 --- a/test/apps/sf-lwc-app/README.md +++ b/test/apps/sf-lwc-app/README.md @@ -19,6 +19,6 @@ From this directory: npm run setup -- -o engrumdev --ignore-conflicts ``` -The setup script copies the local RUM slim bundle into the static resource, deploys the app, assigns the app permission set to the target user, and prints the Home URL. +The setup script copies the local RUM slim bundle into the static resource, deploys the app, assigns the app permission set to the target user, and prints the app-specific Home URL. If prompted for a user. Log into 1Password and use `beltran.bulbarella@datadoghq.com.engrumdev` diff --git a/test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/dummyjson_com.cspTrustedSite-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/dummyjson_com.cspTrustedSite-meta.xml index c874db7bab..fe316e2ee4 100644 --- a/test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/dummyjson_com.cspTrustedSite-meta.xml +++ b/test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/dummyjson_com.cspTrustedSite-meta.xml @@ -3,8 +3,8 @@ false false All - Dummy JSON API for Datadog APM correlation testing - https://dummyjson.com + Sample JSON API for Datadog APM correlation testing + https://sample-json-api.com true true false diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.js b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.js index 5f1a3e4a7d..43ab0391d2 100644 --- a/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.js +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.js @@ -111,7 +111,7 @@ export default class CustomActionButtons extends LightningElement { } getResourceTestUrl(token) { - return `https://dummyjson.com/products/1?${token}` + return `https://sample-json-api.com/products/1?${token}` } getComposedPathNames(event) { diff --git a/test/apps/sf-lwc-app/package.json b/test/apps/sf-lwc-app/package.json index 9576877900..3e4c9820d2 100644 --- a/test/apps/sf-lwc-app/package.json +++ b/test/apps/sf-lwc-app/package.json @@ -1,7 +1,6 @@ { "name": "sf-lwc-app", "private": true, - "version": "1.0.0", "description": "Salesforce Lightning app for browser-sdk Salesforce testing", "scripts": { "setup": "node scripts/setup.mjs" diff --git a/test/apps/sf-lwc-app/scripts/setup.mjs b/test/apps/sf-lwc-app/scripts/setup.mjs index d48ca63a3b..0154302639 100644 --- a/test/apps/sf-lwc-app/scripts/setup.mjs +++ b/test/apps/sf-lwc-app/scripts/setup.mjs @@ -57,9 +57,13 @@ function assignPermissionSet() { } function printAppUrl() { - const result = spawnSync('sf', ['org', 'open', '-p', '/lightning/page/home', '--url-only', '--json', ...orgArgs], { - encoding: 'utf8', - }) + const result = spawnSync( + 'sf', + ['org', 'open', '-p', '/lightning/app/c__SF_LWC_App/page/home', '--url-only', '--json', ...orgArgs], + { + encoding: 'utf8', + } + ) if (result.status !== 0) { process.stderr.write(result.stderr) From 93e80121662f892d96359b87df1cc672f01e152d Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Wed, 10 Jun 2026 13:21:03 +0200 Subject: [PATCH 13/15] fix: rename sample json api url --- ...rustedSite-meta.xml => samplejson_com.cspTrustedSite-meta.xml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/{dummyjson_com.cspTrustedSite-meta.xml => samplejson_com.cspTrustedSite-meta.xml} (100%) diff --git a/test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/dummyjson_com.cspTrustedSite-meta.xml b/test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/samplejson_com.cspTrustedSite-meta.xml similarity index 100% rename from test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/dummyjson_com.cspTrustedSite-meta.xml rename to test/apps/sf-lwc-app/force-app/main/default/cspTrustedSites/samplejson_com.cspTrustedSite-meta.xml From 1e47d830f00b52e73cffdbf690fb961a5c19da43 Mon Sep 17 00:00:00 2001 From: "beltran.bulbarella" Date: Thu, 11 Jun 2026 11:25:52 +0200 Subject: [PATCH 14/15] Format SF html files. Add data-testid --- .../customActionButtons.html | 70 ++++--------------- .../lwc/productExplorer/productExplorer.html | 70 ++++--------------- 2 files changed, 26 insertions(+), 114 deletions(-) diff --git a/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.html b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.html index c75deaaaca..538a60b3fd 100644 --- a/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.html +++ b/test/apps/sf-lwc-app/force-app/main/default/lwc/customActionButtons/customActionButtons.html @@ -1,5 +1,5 @@