@qualified-salesforce/web 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,29 @@ All notable changes to `@qualified-salesforce/web` are recorded here. The format
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the package aims to follow
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.1.3]
8
+
9
+ ### Fixed
10
+
11
+ - **Doc links resolved to a private repo.** The `LICENSE.txt` and `CAPABILITY-REFERENCE.md`
12
+ references in the README, and the `CAPABILITY-REFERENCE.md` reference plus the version links in
13
+ this changelog, were relative or `github.com/FatbearLabs` links — the relative ones npm rewrites
14
+ against the package's `repository` field (a private repo), so a reader got a 404. The file
15
+ references are now plain text noting the files ship in the package (the full license text is
16
+ already reproduced inline, and `CAPABILITY-REFERENCE.md` ships in the tarball), and the changelog
17
+ version links now point at the public npm version pages. No runtime or API change.
18
+
19
+ ## [0.1.2]
20
+
21
+ ### Changed
22
+
23
+ - **License text embedded in the README.** The `## License` section of the package `README.md`
24
+ now reproduces the full proprietary Terms of Use inline, so the license is readable on the npm
25
+ package page. npm shows only "Proprietary. See LICENSE.txt" in its License sidebar and does not
26
+ resolve the relative `LICENSE.txt` link in the rendered README, so a visitor previously could not
27
+ read the terms without opening the Code tab. `LICENSE.txt` still ships in the tarball unchanged.
28
+ No runtime or API change.
29
+
7
30
  ## [0.1.1]
8
31
 
9
32
  ### Added
@@ -74,11 +97,13 @@ All notable changes to `@qualified-salesforce/web` are recorded here. The format
74
97
  - **Origins and CSP.** With every capability denied (the default), the package reaches only your
75
98
  own origin and Qualified's own API / websocket / asset planes — no third-party origin. Each
76
99
  capability's third-party origin and egress, and whether it needs a CSP change, is documented per
77
- key in [`CAPABILITY-REFERENCE.md`](./CAPABILITY-REFERENCE.md).
100
+ key in `CAPABILITY-REFERENCE.md`, shipped in the package.
78
101
  - **Why the gated code ships present-but-inert rather than removed.** A denied capability has no
79
102
  execution path and Qualified cannot enable it remotely, so keeping it inert in one universal,
80
103
  reviewed package is equivalent to absence for what runs in the browser — while letting you enable
81
104
  a feature later by changing one `setup()` option, with no separate build.
82
105
 
83
- [0.1.1]: https://github.com/FatbearLabs/fireball/tree/master/npm-package/package-esm
84
- [0.1.0]: https://github.com/FatbearLabs/fireball/tree/master/npm-package/package-esm
106
+ [0.1.3]: https://www.npmjs.com/package/@qualified-salesforce/web/v/0.1.3
107
+ [0.1.2]: https://www.npmjs.com/package/@qualified-salesforce/web/v/0.1.2
108
+ [0.1.1]: https://www.npmjs.com/package/@qualified-salesforce/web/v/0.1.1
109
+ [0.1.0]: https://www.npmjs.com/package/@qualified-salesforce/web/v/0.1.0
package/README.md CHANGED
@@ -40,7 +40,7 @@ renders the widget unstyled.
40
40
  Every optional feature is **off unless you enroll it** in the `capabilities` option. Omitting
41
41
  `capabilities` (or passing `[]`) leaves them all off.
42
42
 
43
- See [`CAPABILITY-REFERENCE.md`](./CAPABILITY-REFERENCE.md) for the full list — what each capability
43
+ See `CAPABILITY-REFERENCE.md`, shipped in the package, for the full list — what each capability
44
44
  does, the data it sends, whether it needs a CSP change, and its default.
45
45
 
46
46
  ## TypeScript
@@ -62,4 +62,70 @@ const qualified = await setup({
62
62
 
63
63
  ## License
64
64
 
65
- Proprietary. See [`LICENSE.txt`](./LICENSE.txt).
65
+ Proprietary. The full text is reproduced below and also ships in the package as `LICENSE.txt`.
66
+
67
+ ### Terms of Use
68
+
69
+ Copyright 2026 Salesforce, Inc. All rights reserved.
70
+
71
+ These Terms of Use govern the download, installation, and/or use of this software provided by
72
+ Salesforce, Inc. ("Salesforce") (the "Software"), were last updated on April 15, 2025, and
73
+ constitute a legally binding agreement between you and Salesforce. If you do not agree to these
74
+ Terms of Use, do not install or use the Software.
75
+
76
+ Salesforce grants you a worldwide, non-exclusive, no-charge, royalty-free copyright license to
77
+ reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and
78
+ distribute the Software and derivative works subject to these Terms. These Terms shall be included
79
+ in all copies or substantial portions of the Software.
80
+
81
+ Subject to the limited rights expressly granted hereunder, Salesforce reserves all rights, title,
82
+ and interest in and to all intellectual property subsisting in the Software. No rights are granted
83
+ to you hereunder other than as expressly set forth herein. Users residing in countries on the
84
+ United States Office of Foreign Assets Control sanction list, or which are otherwise subject to a US
85
+ export embargo, may not use the Software.
86
+
87
+ Implementation of the Software may require development work, for which you are responsible. The
88
+ Software may contain bugs, errors and incompatibilities and is made available on an AS IS basis
89
+ without support, updates, or service level commitments.
90
+
91
+ Salesforce reserves the right at any time to modify, suspend, or discontinue, the Software (or any
92
+ part thereof) with or without notice. You agree that Salesforce shall not be liable to you or to any
93
+ third party for any modification, suspension, or discontinuance.
94
+
95
+ You agree to defend Salesforce against any claim, demand, suit or proceeding made or brought against
96
+ Salesforce by a third party arising out of or accruing from (a) your use of the Software, and (b)
97
+ any application you develop with the Software that infringes any copyright, trademark, trade secret,
98
+ trade dress, patent, or other intellectual property right of any person or defames any person or
99
+ violates their rights of publicity or privacy (each a "Claim Against Salesforce"), and will
100
+ indemnify Salesforce from any damages, attorney fees, and costs finally awarded against Salesforce
101
+ as a result of, or for any amounts paid by Salesforce under a settlement approved by you in writing
102
+ of, a Claim Against Salesforce, provided Salesforce (x) promptly gives you written notice of the
103
+ Claim Against Salesforce, (y) gives you sole control of the defense and settlement of the Claim
104
+ Against Salesforce (except that you may not settle any Claim Against Salesforce unless it
105
+ unconditionally releases Salesforce of all liability), and (z) gives you all reasonable assistance,
106
+ at your expense.
107
+
108
+ WITHOUT LIMITING THE GENERALITY OF THE FOREGOING, THE SOFTWARE IS NOT SUPPORTED AND IS PROVIDED "AS
109
+ IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. IN NO EVENT SHALL SALESFORCE HAVE ANY
110
+ LIABILITY FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, INDIRECT, SPECIAL, INCIDENTAL,
111
+ PUNITIVE, OR CONSEQUENTIAL DAMAGES, OR DAMAGES BASED ON LOST PROFITS, DATA, OR USE, IN CONNECTION
112
+ WITH THE SOFTWARE, HOWEVER CAUSED AND WHETHER IN CONTRACT, TORT, OR UNDER ANY OTHER THEORY OF
113
+ LIABILITY, WHETHER OR NOT YOU HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
114
+
115
+ These Terms of Use shall be governed exclusively by the internal laws of the State of California,
116
+ without regard to its conflicts of laws rules. Each party hereby consents to the exclusive
117
+ jurisdiction of the state and federal courts located in San Francisco County, California to
118
+ adjudicate any dispute arising out of or relating to these Terms of Use and the download,
119
+ installation, and/or use of the Software. Except as expressly stated herein, these Terms of Use
120
+ constitute the entire agreement between the parties, and supersede all prior and contemporaneous
121
+ agreements, proposals, or representations, written or oral, concerning their subject matter. No
122
+ modification, amendment, or waiver of any provision of these Terms of Use shall be effective unless
123
+ it is by an update to these Terms of Use that Salesforce makes available, or is in writing and
124
+ signed by the party against whom the modification, amendment, or waiver is to be asserted.
125
+
126
+ Data Privacy: Salesforce may collect, process, and store device, system, and other information
127
+ related to your use of the Software. This information includes, but is not limited to, IP address,
128
+ user metrics, and other data ("Usage Data"). Salesforce may use Usage Data for analytics, product
129
+ development, and marketing purposes. You acknowledge that files generated in conjunction with the
130
+ Software may contain sensitive or confidential data, and you are solely responsible for anonymizing
131
+ and protecting such data.
@@ -3,7 +3,7 @@ import { _tagged_template_literal_loose, lib } from "./js/4101.js";
3
3
 
4
4
  function constants_generated_templateObject() {
5
5
  const data = _tagged_template_literal_loose([
6
- "\n query CurrentAccount {\n account {\n id\n name\n firstName\n lastName\n email\n phoneNumber\n jobTitle\n avatarUrl\n timeZone\n preferredConversationLocales\n emailSignature\n useGmailSignature\n mailingConnection {\n id\n type\n }\n meetingConferenceDetails\n calendarInviteBody\n calendarMinimumNotice\n calendarBuffer\n calendarAvailability {\n day\n start\n end\n __typename\n }\n mobileNotificationSchedule {\n day\n start\n end\n __typename\n }\n enableReturnVisitorAlerts\n enableVoiceAlerts\n enableBlurBackground\n defaultNotificationSound\n dismissedDesktopAnnouncement\n waitingForRepNotificationSound\n waitingForRepVoiceAlert\n role\n permissionsAdmin\n permissionsCsm\n permissionsChat\n permissionsMeeting\n permissionsViewAllVisitors\n permissionsEditAllTeamSettings\n permissionsEditTeammates\n permissionsEditGroups\n permissionsEditTeamCalendar\n permissionsEditBusinessHours\n permissionsEditSso\n permissionsEditMessenger\n permissionsEditVsiitorFields\n permissionsEditSegments\n permissionsEditLiveStreams\n permissionsEditRouting\n permissionsEditMacros\n permissionsEditSalesforce\n permissionsEditPardot\n permissionsEditPiperEmail\n permissionsPublishPiperEmail\n permissionsViewPiperEmailResponses\n permissionsEditMarketo\n permissionsEditHubspot\n permissionsEditEloqua\n permissionsEditOutreach\n permissionsEditClearbit\n permissionsEditSixsense\n permissionsEditDemandbase\n permissionsEditSlack\n permissionsEditMicrosoft\n permissionsEditZoominfo\n permissionsEditSalesloft\n permissionsEditLinkedin\n permissionsEditGoogle\n permissionsViewAllStatistics\n permissionsEditAnalytics\n permissionsExportReports\n permissionsBotView\n permissionsBotEdit\n permissionsBotPublish\n permissionsEditMeetingTypes\n permissionsEditMeetingRouting\n permissionsEditAutoPounce\n permissionsShareDashboards\n permissionsViewRevenueAndPipelineDashboard\n available\n videoAvailable\n autoPounceAvailable\n snoozeEndsAt\n hasMobile\n mobileRequestForChatSound\n mobileMessageReceivedSound\n __typename\n salesforceConnection {\n id\n __typename\n updatedAt\n username\n }\n calendarConnection {\n ...CalendarConnection\n }\n slackConnection {\n id\n __typename\n }\n microsoftConnection {\n id\n __typename\n invalid\n }\n externalMeetingConnection {\n id\n __typename\n updatedAt\n email\n type\n invalid\n invalidAt\n scopes\n }\n macros {\n ...Macro\n }\n announcements {\n ...Announcement\n }\n team {\n id\n allowlistedDomains\n denylistedVisitorStreamDomains\n company\n appexchangeCompanyName\n autoUpdateSalesforceExternalRecords\n token\n phoneNumber\n countryCode\n features\n featureAlternatives {\n flag\n alternative\n }\n preferencesSendGaEvents\n preferencesSendFbEvents\n preferencesShowIdleVisitorsDefault\n preferencesShowSalesforceMatchSettings\n preferencesSfdcOauthPromptSelectAccount\n preferencesDisableScimRoleSync\n preferencesDisableDesktopAppAnnouncement\n defaultSyncActionType\n samlEnabled\n samlVerified\n scimEnabled\n identityProvider\n maximumExportRows\n createdAt\n signalsToken\n privacyPolicyMaxLength\n companyEmail\n companyAddress\n address\n city\n state\n postalCode\n country\n mailboxesNeedAttention\n __typename\n activeLinkedinAdAccounts {\n ...LinkedInAdAccount\n }\n activeGoogleAdAccounts {\n ...GoogleAdAccount\n }\n salesforceConnection {\n id\n __typename\n updatedAt\n username\n invalid\n sandbox\n host\n instanceUrl\n isSyncing\n lastSyncTime\n lastSyncError\n matchSettings {\n __typename\n type\n contactExternalFieldId\n leadExternalFieldId\n }\n }\n pardotConnection {\n id\n __typename\n email\n dynamicContentConfigured\n activityTrackingConfigured\n invalid\n demo\n businessUnitId\n username\n }\n zoomInfoConnection {\n id\n __typename\n email\n invalid\n enterpriseEnabled\n websightsEnabled\n rateLimitedEnterprise\n rateLimitedWebsights\n }\n marketoConnection {\n id\n __typename\n munchkinId\n invalid\n }\n clearbitConnection {\n id\n __typename\n secretKeyMasked\n revealEnabled\n enrichmentEnabled\n confidenceThreshold\n }\n sixsenseConnection {\n id\n __typename\n secretKeyMasked\n confidenceThreshold\n productLines\n }\n slackConnection {\n id\n __typename\n invalid\n authorizedUserEmailScopes\n version\n }\n piperSlackConnection {\n id\n __typename\n invalid\n authorizedUserEmailScopes\n }\n microsoftConnection {\n id\n __typename\n invalid\n email\n }\n demandbaseConnection {\n id\n __typename\n secretKeyMasked\n version\n }\n demandbaseSharedRevealEnabled\n demandbaseSharedEnrichmentEnabled\n demandbaseSharedConfidenceThreshold\n demandbaseCustomizedMsa\n hubspotConnection {\n id\n __typename\n visitorAssociationConfigured\n hasAllScopes\n invalid\n identifier\n formsLastSyncedAt\n }\n linkedinConnection {\n ...LinkedInConnection\n }\n facebookConnection {\n ...FacebookConnection\n }\n eloquaConnection {\n id\n __typename\n visitorAssociationConfigured\n invalid\n }\n outreachGalaxyConnection {\n id\n __typename\n invalid\n }\n salesloftConnection {\n id\n __typename\n invalid\n }\n gongConnection {\n id\n __typename\n invalid\n }\n googleConnection {\n ...GoogleConnection\n }\n users {\n ...TeamUser\n }\n inactiveUsers {\n ...TeamUser\n }\n groups {\n id\n __typename\n name\n members {\n id\n __typename\n }\n }\n segments {\n ...SegmentMeta\n }\n calendarEventTypes {\n id\n name\n duration\n sendReminder\n eventTitle\n eventBody\n enableMeetingLink\n enableCalendarEventTypeInChat\n enableNoShowReporting\n removeCancelOrReschedule\n __typename\n }\n macros {\n ...Macro\n }\n fields {\n __typename\n id\n name\n apiName\n type\n hidden\n listVisible\n }\n variables {\n ...Variable\n }\n dashboards {\n __typename\n id\n type\n settings {\n ...DashboardSettings\n }\n }\n listGroupings {\n ...ListGrouping\n }\n businessHours {\n __typename\n days\n startHour\n endHour\n timeZone\n }\n calendarEventExclusionDates {\n ...CalendarEventExclusionDate\n }\n autoPounceSettings {\n missedConnections {\n responseTimeLimitSeconds\n }\n }\n copilotEnabledMessageTransformationTypes\n aiInteractionReports {\n nodes {\n userId\n messageId\n }\n }\n customWaitForIdentifiersTime\n }\n activeMeeting {\n id\n status\n token\n startedAt\n visitorId\n userId\n __typename\n }\n }\n }\n fragment TeamUser on User {\n __typename\n id\n email\n name\n avatarUrl\n available\n snoozeEndsAt\n role\n customAdminPermissions\n permissionsAdmin\n permissionsViewAllVisitors\n permissionsEditAllTeamSettings\n permissionsEditTeammates\n permissionsEditGroups\n permissionsEditTeamCalendar\n permissionsEditBusinessHours\n permissionsEditSso\n permissionsEditMessenger\n permissionsEditVsiitorFields\n permissionsEditSegments\n permissionsEditLiveStreams\n permissionsEditRouting\n permissionsEditMacros\n permissionsEditSalesforce\n permissionsEditPardot\n permissionsEditPiperEmail\n permissionsPublishPiperEmail\n permissionsViewPiperEmailResponses\n permissionsEditMarketo\n permissionsEditHubspot\n permissionsEditEloqua\n permissionsEditOutreach\n permissionsEditClearbit\n permissionsEditSixsense\n permissionsEditDemandbase\n permissionsEditSlack\n permissionsEditMicrosoft\n permissionsEditZoominfo\n permissionsEditSalesloft\n permissionsEditLinkedin\n permissionsEditGoogle\n permissionsViewAllStatistics\n permissionsEditAnalytics\n permissionsExportReports\n permissionsBotView\n permissionsBotEdit\n permissionsBotPublish\n permissionsEditMeetingTypes\n permissionsEditMeetingRouting\n permissionsEditAutoPounce\n permissionsShareDashboards\n permissionsViewRevenueAndPipelineDashboard\n status\n lastOnlineAt\n authenticationState\n lastInviteSentAt\n lastSamlBindingLinkSentAt\n calendarReminderSentAt\n mailingConnectionReminderSentAt\n actionRequiredReasons\n meetingQueuesActionRequiredReasons\n groups {\n name\n }\n salesforceConnection {\n __typename\n id\n updatedAt\n username\n }\n calendarInviteBody\n meetingConferenceDetails\n calendarConnection {\n ...CalendarConnection\n }\n mailingConnection {\n ...CalendarConnection\n }\n slackConnection {\n id\n __typename\n }\n microsoftConnection {\n id\n __typename\n invalid\n }\n externalMeetingConnection {\n id\n type\n __typename\n }\n userProfile {\n id\n __typename\n name\n type\n }\n }\n fragment CalendarConnection on CalendarConnection {\n id\n __typename\n createdAt\n updatedAt\n email\n type\n invalid\n invalidAt\n scopes\n }\n\n fragment Macro on Macro {\n __typename\n id\n shared\n name\n steps {\n __typename\n opcode\n operands {\n __typename\n text\n }\n }\n }\n\n fragment SegmentMeta on Segment {\n __typename\n id\n name\n }\n\n fragment Announcement on Announcement {\n __typename\n id\n ctaText\n ctaUrl\n message\n type\n startsAt\n endsAt\n }\n\n fragment Variable on Variable {\n __typename\n key\n category\n type\n group\n source\n label\n require\n }\n\n fragment DashboardSettings on DashboardSettings {\n __typename\n startDate\n endDate\n currencyField\n conversationDateThreshold\n opportunityTypes\n status\n stages\n attributedOpportunityTiles {\n title\n color\n attributionModel\n columns {\n expression\n width\n }\n orderBy\n orderReversed\n matchTypes\n leadSourceFieldValues\n }\n }\n\n fragment ListGrouping on ListGrouping {\n __typename\n id\n name\n color\n position\n bots {\n __typename\n id\n loadingDraftScript\n }\n segments {\n __typename\n id\n }\n listGroupingSetting {\n __typename\n id\n listGroupingId\n settingsSendPushNotification\n settingsDisableConsoleNotification\n settingsEnableVoiceNotifications\n sound\n }\n autoPounce\n }\n\n fragment LinkedInConnection on LinkedInConnection {\n id\n __typename\n firstName\n lastName\n displayName\n accountId\n invalid\n }\n\n fragment LinkedInAdAccount on LinkedInAdAccount {\n id\n __typename\n externalId\n name\n }\n\n fragment GoogleConnection on GoogleConnection {\n id\n __typename\n email\n loginCustomerId\n invalid\n }\n\n fragment GoogleAdAccount on GoogleAdAccount {\n id\n __typename\n externalId\n name\n }\n\n fragment FacebookConnection on FacebookConnection {\n id\n __typename\n name\n email\n }\n\n fragment CalendarEventExclusionDate on CalendarEventExclusionDate {\n __typename\n id\n startDate\n endDate\n }\n"
6
+ "\n query CurrentAccount {\n account {\n id\n name\n firstName\n lastName\n email\n phoneNumber\n jobTitle\n avatarUrl\n timeZone\n preferredConversationLocales\n emailSignature\n useGmailSignature\n mailingConnection {\n id\n type\n }\n meetingConferenceDetails\n calendarInviteBody\n calendarMinimumNotice\n calendarBuffer\n calendarAvailability {\n day\n start\n end\n __typename\n }\n mobileNotificationSchedule {\n day\n start\n end\n __typename\n }\n enableReturnVisitorAlerts\n enableVoiceAlerts\n enableBlurBackground\n defaultNotificationSound\n dismissedDesktopAnnouncement\n waitingForRepNotificationSound\n waitingForRepVoiceAlert\n role\n permissionsAdmin\n permissionsCsm\n permissionsChat\n permissionsMeeting\n permissionsViewAllVisitors\n permissionsEditAllTeamSettings\n permissionsEditTeammates\n permissionsEditGroups\n permissionsEditTeamCalendar\n permissionsEditBusinessHours\n permissionsEditSso\n permissionsEditMessenger\n permissionsEditVsiitorFields\n permissionsEditSegments\n permissionsEditLiveStreams\n permissionsEditRouting\n permissionsEditMacros\n permissionsEditSalesforce\n permissionsEditPardot\n permissionsEditPiperEmail\n permissionsPublishPiperEmail\n permissionsViewPiperEmailResponses\n permissionsEditMarketo\n permissionsEditHubspot\n permissionsEditEloqua\n permissionsEditOutreach\n permissionsEditClearbit\n permissionsEditSixsense\n permissionsEditDemandbase\n permissionsEditSlack\n permissionsEditMicrosoft\n permissionsEditZoominfo\n permissionsEditSalesloft\n permissionsEditLinkedin\n permissionsEditGoogle\n permissionsViewAllStatistics\n permissionsEditAnalytics\n permissionsExportReports\n permissionsBotView\n permissionsBotEdit\n permissionsBotPublish\n permissionsEditMeetingTypes\n permissionsEditMeetingRouting\n permissionsEditAutoPounce\n permissionsShareDashboards\n permissionsViewRevenueAndPipelineDashboard\n available\n videoAvailable\n autoPounceAvailable\n snoozeEndsAt\n hasMobile\n mobileRequestForChatSound\n mobileMessageReceivedSound\n __typename\n salesforceConnection {\n id\n __typename\n updatedAt\n username\n }\n calendarConnection {\n ...CalendarConnection\n }\n slackConnection {\n id\n __typename\n }\n microsoftConnection {\n id\n __typename\n invalid\n }\n externalMeetingConnection {\n id\n __typename\n updatedAt\n email\n type\n invalid\n invalidAt\n scopes\n }\n macros {\n ...Macro\n }\n announcements {\n ...Announcement\n }\n team {\n id\n allowlistedDomains\n denylistedVisitorStreamDomains\n company\n appexchangeCompanyName\n autoUpdateSalesforceExternalRecords\n token\n phoneNumber\n countryCode\n features\n featureAlternatives {\n flag\n alternative\n }\n preferencesSendGaEvents\n preferencesSendFbEvents\n preferencesShowIdleVisitorsDefault\n preferencesShowSalesforceMatchSettings\n preferencesSfdcOauthPromptSelectAccount\n preferencesDisableScimRoleSync\n preferencesDisableDesktopAppAnnouncement\n defaultSyncActionType\n samlEnabled\n samlVerified\n scimEnabled\n identityProvider\n maximumExportRows\n createdAt\n signalsToken\n privacyPolicyMaxLength\n companyEmail\n companyAddress\n address\n city\n state\n postalCode\n country\n mailboxesNeedAttention\n __typename\n activeLinkedinAdAccounts {\n ...LinkedInAdAccount\n }\n activeGoogleAdAccounts {\n ...GoogleAdAccount\n }\n salesforceConnection {\n id\n __typename\n updatedAt\n username\n invalid\n sandbox\n host\n instanceUrl\n isSyncing\n lastSyncTime\n lastSyncError\n matchSettings {\n __typename\n type\n contactExternalFieldId\n leadExternalFieldId\n }\n }\n pardotConnection {\n id\n __typename\n email\n dynamicContentConfigured\n activityTrackingConfigured\n invalid\n demo\n businessUnitId\n username\n }\n zoomInfoConnection {\n id\n __typename\n email\n invalid\n enterpriseEnabled\n websightsEnabled\n rateLimitedEnterprise\n rateLimitedWebsights\n }\n marketoConnection {\n id\n __typename\n munchkinId\n invalid\n }\n clearbitConnection {\n id\n __typename\n secretKeyMasked\n revealEnabled\n enrichmentEnabled\n confidenceThreshold\n }\n sixsenseConnection {\n id\n __typename\n secretKeyMasked\n confidenceThreshold\n productLines\n }\n slackConnection {\n id\n __typename\n invalid\n authorizedUserEmailScopes\n version\n }\n piperSlackConnection {\n id\n __typename\n invalid\n authorizedUserEmailScopes\n }\n microsoftConnection {\n id\n __typename\n invalid\n email\n }\n demandbaseConnection {\n id\n __typename\n secretKeyMasked\n version\n }\n demandbaseSharedRevealEnabled\n demandbaseSharedEnrichmentEnabled\n demandbaseSharedConfidenceThreshold\n demandbaseCustomizedMsa\n demandbaseEnrichmentMsaAcceptedAt\n demandbaseEnrichmentMsaAcceptedBy\n hubspotConnection {\n id\n __typename\n visitorAssociationConfigured\n hasAllScopes\n invalid\n identifier\n formsLastSyncedAt\n }\n linkedinConnection {\n ...LinkedInConnection\n }\n facebookConnection {\n ...FacebookConnection\n }\n eloquaConnection {\n id\n __typename\n visitorAssociationConfigured\n invalid\n }\n outreachGalaxyConnection {\n id\n __typename\n invalid\n }\n salesloftConnection {\n id\n __typename\n invalid\n }\n gongConnection {\n id\n __typename\n invalid\n }\n googleConnection {\n ...GoogleConnection\n }\n users {\n ...TeamUser\n }\n inactiveUsers {\n ...TeamUser\n }\n groups {\n id\n __typename\n name\n members {\n id\n __typename\n }\n }\n segments {\n ...SegmentMeta\n }\n calendarEventTypes {\n id\n name\n duration\n sendReminder\n eventTitle\n eventBody\n enableMeetingLink\n enableCalendarEventTypeInChat\n enableNoShowReporting\n removeCancelOrReschedule\n __typename\n }\n macros {\n ...Macro\n }\n fields {\n __typename\n id\n name\n apiName\n type\n hidden\n listVisible\n }\n variables {\n ...Variable\n }\n dashboards {\n __typename\n id\n type\n settings {\n ...DashboardSettings\n }\n }\n listGroupings {\n ...ListGrouping\n }\n businessHours {\n __typename\n days\n startHour\n endHour\n timeZone\n }\n calendarEventExclusionDates {\n ...CalendarEventExclusionDate\n }\n autoPounceSettings {\n missedConnections {\n responseTimeLimitSeconds\n }\n }\n copilotEnabledMessageTransformationTypes\n aiInteractionReports {\n nodes {\n userId\n messageId\n }\n }\n customWaitForIdentifiersTime\n }\n activeMeeting {\n id\n status\n token\n startedAt\n visitorId\n userId\n __typename\n }\n }\n }\n fragment TeamUser on User {\n __typename\n id\n email\n name\n avatarUrl\n available\n snoozeEndsAt\n role\n customAdminPermissions\n permissionsAdmin\n permissionsViewAllVisitors\n permissionsEditAllTeamSettings\n permissionsEditTeammates\n permissionsEditGroups\n permissionsEditTeamCalendar\n permissionsEditBusinessHours\n permissionsEditSso\n permissionsEditMessenger\n permissionsEditVsiitorFields\n permissionsEditSegments\n permissionsEditLiveStreams\n permissionsEditRouting\n permissionsEditMacros\n permissionsEditSalesforce\n permissionsEditPardot\n permissionsEditPiperEmail\n permissionsPublishPiperEmail\n permissionsViewPiperEmailResponses\n permissionsEditMarketo\n permissionsEditHubspot\n permissionsEditEloqua\n permissionsEditOutreach\n permissionsEditClearbit\n permissionsEditSixsense\n permissionsEditDemandbase\n permissionsEditSlack\n permissionsEditMicrosoft\n permissionsEditZoominfo\n permissionsEditSalesloft\n permissionsEditLinkedin\n permissionsEditGoogle\n permissionsViewAllStatistics\n permissionsEditAnalytics\n permissionsExportReports\n permissionsBotView\n permissionsBotEdit\n permissionsBotPublish\n permissionsEditMeetingTypes\n permissionsEditMeetingRouting\n permissionsEditAutoPounce\n permissionsShareDashboards\n permissionsViewRevenueAndPipelineDashboard\n status\n lastOnlineAt\n authenticationState\n lastInviteSentAt\n lastSamlBindingLinkSentAt\n calendarReminderSentAt\n mailingConnectionReminderSentAt\n actionRequiredReasons\n meetingQueuesActionRequiredReasons\n groups {\n name\n }\n salesforceConnection {\n __typename\n id\n updatedAt\n username\n }\n calendarInviteBody\n meetingConferenceDetails\n calendarConnection {\n ...CalendarConnection\n }\n mailingConnection {\n ...CalendarConnection\n }\n slackConnection {\n id\n __typename\n }\n microsoftConnection {\n id\n __typename\n invalid\n }\n externalMeetingConnection {\n id\n type\n __typename\n }\n userProfile {\n id\n __typename\n name\n type\n }\n }\n fragment CalendarConnection on CalendarConnection {\n id\n __typename\n createdAt\n updatedAt\n email\n type\n invalid\n invalidAt\n scopes\n }\n\n fragment Macro on Macro {\n __typename\n id\n shared\n name\n steps {\n __typename\n opcode\n operands {\n __typename\n text\n }\n }\n }\n\n fragment SegmentMeta on Segment {\n __typename\n id\n name\n }\n\n fragment Announcement on Announcement {\n __typename\n id\n ctaText\n ctaUrl\n message\n type\n startsAt\n endsAt\n }\n\n fragment Variable on Variable {\n __typename\n key\n category\n type\n group\n source\n label\n require\n }\n\n fragment DashboardSettings on DashboardSettings {\n __typename\n startDate\n endDate\n currencyField\n conversationDateThreshold\n opportunityTypes\n status\n stages\n attributedOpportunityTiles {\n title\n color\n attributionModel\n columns {\n expression\n width\n }\n orderBy\n orderReversed\n matchTypes\n leadSourceFieldValues\n }\n }\n\n fragment ListGrouping on ListGrouping {\n __typename\n id\n name\n color\n position\n bots {\n __typename\n id\n loadingDraftScript\n }\n segments {\n __typename\n id\n }\n listGroupingSetting {\n __typename\n id\n listGroupingId\n settingsSendPushNotification\n settingsDisableConsoleNotification\n settingsEnableVoiceNotifications\n sound\n }\n autoPounce\n }\n\n fragment LinkedInConnection on LinkedInConnection {\n id\n __typename\n firstName\n lastName\n displayName\n accountId\n invalid\n }\n\n fragment LinkedInAdAccount on LinkedInAdAccount {\n id\n __typename\n externalId\n name\n }\n\n fragment GoogleConnection on GoogleConnection {\n id\n __typename\n email\n loginCustomerId\n invalid\n }\n\n fragment GoogleAdAccount on GoogleAdAccount {\n id\n __typename\n externalId\n name\n }\n\n fragment FacebookConnection on FacebookConnection {\n id\n __typename\n name\n email\n }\n\n fragment CalendarEventExclusionDate on CalendarEventExclusionDate {\n __typename\n id\n startDate\n endDate\n }\n"
7
7
  ]);
8
8
  constants_generated_templateObject = function _templateObject() {
9
9
  return data;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qualified-salesforce/web",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Embeddable Qualified messenger widget (text-only ESM build).",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "private": false,
@@ -282,6 +282,7 @@ const SNIPPET_FEATURE_FLAGS = (/* unused pure expression or super */ null && ([
282
282
  "aisdrChatEmailValidation",
283
283
  "bookerJourneySLI",
284
284
  "clientAPIHandleWidgetEvents",
285
+ "clientExperienceJourneyTelemetry",
285
286
  "customerZero",
286
287
  "demo",
287
288
  "demoKillSessionShortcut",
@@ -0,0 +1,106 @@
1
+ const JOURNEY_SCHEMA_VERSION = 1;
2
+ const JOURNEY_ID_PATTERN = /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i;
3
+ function isJourneyId(value) {
4
+ return typeof value === "string" && value.length === 36 && JOURNEY_ID_PATTERN.test(value);
5
+ }
6
+ function isJourneyReadyResult(value) {
7
+ return value === "qualification" || value === "booker" || value === "messenger";
8
+ }
9
+ function isJourneyResultType(value) {
10
+ return isJourneyReadyResult(value) || value === "fallback" || value === "no_content" || value === "error" || value === "unknown";
11
+ }
12
+ function isJourneyUiSurface(value) {
13
+ return value === "legacy" || value === "multimodal" || value === "inline";
14
+ }
15
+ function isJourneyBookerDetail(value) {
16
+ return value === undefined || value === "slots" || value === "no_availability";
17
+ }
18
+ function isJourneyResultId(value) {
19
+ return typeof value === "string" && value.trim().length > 0;
20
+ }
21
+ const NON_SUCCESS_OUTCOMES = [
22
+ "fallback",
23
+ "no_content",
24
+ "visible_error",
25
+ "provider_error",
26
+ "timeout",
27
+ "abandoned",
28
+ "superseded",
29
+ "unknown",
30
+ "redirect_initiated"
31
+ ];
32
+ function isJourneyNonSuccessOutcome(value) {
33
+ return typeof value === "string" && NON_SUCCESS_OUTCOMES.includes(value);
34
+ }
35
+ function isValidJourneyStart(start) {
36
+ if (!start || typeof start !== "object" || Array.isArray(start)) return false;
37
+ const fields = start;
38
+ if (!isJourneyId(fields.journeyId)) return false;
39
+ if (typeof fields.triggerKey !== "string") return false;
40
+ if (fields.supersessionKey !== undefined && typeof fields.supersessionKey !== "string") return false;
41
+ if (fields.navigationMode !== "same_page" && fields.navigationMode !== "redirect") return false;
42
+ if (typeof fields.entryPath !== "string" || typeof fields.startAnchor !== "string") return false;
43
+ if (fields.journeyKind === "button") {
44
+ return [
45
+ "smart_cta",
46
+ "manual",
47
+ "api",
48
+ "pre_init",
49
+ "inline_auto_start",
50
+ "tooling"
51
+ ].includes(fields.entryPath) && [
52
+ "native_click",
53
+ "api_call",
54
+ "initialization"
55
+ ].includes(fields.startAnchor);
56
+ }
57
+ if (fields.journeyKind === "smart_form") {
58
+ return [
59
+ "custom",
60
+ "hubspot_v3",
61
+ "hubspot_v4",
62
+ "marketo",
63
+ "pardot",
64
+ "api",
65
+ "pre_init",
66
+ "provider_only",
67
+ "replay_without_root",
68
+ "tooling"
69
+ ].includes(fields.entryPath) && [
70
+ "validated_native_submit",
71
+ "api_call",
72
+ "initialization",
73
+ "provider_success",
74
+ "replay"
75
+ ].includes(fields.startAnchor);
76
+ }
77
+ return false;
78
+ }
79
+ /** Runtime boundary shared by the tracker and transport; TypeScript is erased at iframe bridges. */ function isValidJourneyTerminal(record) {
80
+ if (!record || typeof record !== "object") return false;
81
+ if (!isValidJourneyStart(record.start)) return false;
82
+ const { outcome, resultType, uiSurface, correlationQuality, timingWithheldReason } = record;
83
+ if (outcome !== "success" && !isJourneyNonSuccessOutcome(outcome)) return false;
84
+ if (correlationQuality !== "exact" && correlationQuality !== "ambiguous") return false;
85
+ if (resultType !== undefined && !isJourneyResultType(resultType)) return false;
86
+ if (uiSurface !== undefined && !isJourneyUiSurface(uiSurface)) return false;
87
+ if (!isJourneyBookerDetail(record.bookerResultDetail)) return false;
88
+ if (timingWithheldReason !== undefined && ![
89
+ "backgrounded",
90
+ "ambiguous",
91
+ "missing_clock",
92
+ "invalid_clock"
93
+ ].includes(timingWithheldReason)) return false;
94
+ const durations = [
95
+ record.durationMs,
96
+ record.preRequestMs,
97
+ record.requestToResponseMs,
98
+ record.responseToReadyMs
99
+ ];
100
+ if (durations.some((value)=>value !== undefined && (typeof value !== "number" || !Number.isFinite(value) || value < 0))) return false;
101
+ if (timingWithheldReason !== undefined && durations.some((value)=>value !== undefined)) return false;
102
+ if (correlationQuality === "ambiguous" && timingWithheldReason === undefined) return false;
103
+ return outcome !== "success" || isJourneyReadyResult(resultType) && isJourneyUiSurface(uiSurface) && (record.durationMs !== undefined || timingWithheldReason !== undefined);
104
+ }
105
+
106
+ export { JOURNEY_SCHEMA_VERSION, isJourneyBookerDetail, isJourneyId, isJourneyNonSuccessOutcome, isJourneyReadyResult, isJourneyResultId, isJourneyResultType, isJourneyUiSurface, isValidJourneyStart, isValidJourneyTerminal };
@@ -0,0 +1,387 @@
1
+ import { __webpack_require__ } from "../../js/runtime.js";
2
+ import { isValidJourneyTerminal, isJourneyId, isValidJourneyStart, isJourneyBookerDetail, isJourneyResultId, isJourneyResultType, isJourneyNonSuccessOutcome, isJourneyUiSurface, isJourneyReadyResult } from "./schema.js";
3
+ import { _extends } from "../../js/4101.js";
4
+ import "../utils/native_globals.js";
5
+
6
+ /* provided dependency */ var __nativeDocumentAddEventListener = __webpack_require__(68034).Qj;
7
+ /* provided dependency */ var __nativeWindowAddEventListener = __webpack_require__(68034).zh;
8
+ /* provided dependency */ var __nativeDocumentRemoveEventListener = __webpack_require__(68034).LA;
9
+ /* provided dependency */ var __nativeWindowRemoveEventListener = __webpack_require__(68034).kZ;
10
+
11
+
12
+ function elapsed(start, finish) {
13
+ if (start === undefined || finish === undefined) return undefined;
14
+ const value = Math.round(finish - start);
15
+ return Number.isFinite(value) && value >= 0 ? value : undefined;
16
+ }
17
+ class ClientExperienceJourneyTracker {
18
+ start(input) {
19
+ try {
20
+ const suppliedJourneyId = isJourneyId(input.journeyId) ? input.journeyId : undefined;
21
+ if (suppliedJourneyId && this.activeById.has(suppliedJourneyId)) return suppliedJourneyId;
22
+ const overlapping = input.supersessionKey ? [
23
+ ...this.activeById.values()
24
+ ].filter((journey)=>journey.start.supersessionKey === input.supersessionKey) : [];
25
+ const journeyId = suppliedJourneyId || this.createId();
26
+ if (!isJourneyId(journeyId)) return "";
27
+ // Only an explicitly supplied active ID is a retry. A generator collision is a new attempt.
28
+ if (this.activeById.has(journeyId)) return "";
29
+ const startedAtMs = this.readNow();
30
+ if (overlapping.length > 0 && startedAtMs === undefined) return "";
31
+ const start = _extends({}, input, {
32
+ journeyId
33
+ });
34
+ if (!isValidJourneyStart(start)) return "";
35
+ const hidden = this.isHidden();
36
+ const journey = _extends({
37
+ start,
38
+ startedAtMs,
39
+ checkpoints: {},
40
+ diagnosticKeys: new Set(),
41
+ correlationQuality: "exact"
42
+ }, hidden ? {
43
+ timingWithheldReason: "backgrounded"
44
+ } : startedAtMs === undefined ? {
45
+ timingWithheldReason: "missing_clock"
46
+ } : {});
47
+ for (const existing of overlapping){
48
+ this.finishLifecycle(existing, "superseded");
49
+ }
50
+ this.activeById.set(journeyId, journey);
51
+ this.ensureListeners();
52
+ try {
53
+ this.transport.emitStart(start);
54
+ } catch (unused) {
55
+ // Telemetry cannot affect product behavior.
56
+ }
57
+ return journeyId;
58
+ } catch (unused) {
59
+ return isJourneyId(input == null ? void 0 : input.journeyId) ? input.journeyId : "";
60
+ }
61
+ }
62
+ hasActiveJourney(journeyKind) {
63
+ return [
64
+ ...this.activeById.values()
65
+ ].some((journey)=>journey.start.journeyKind === journeyKind);
66
+ }
67
+ checkpoint(journeyId, checkpoint) {
68
+ try {
69
+ const journey = this.activeById.get(journeyId);
70
+ if (!journey || journey.checkpoints[checkpoint] !== undefined) return;
71
+ const value = this.readNow();
72
+ if (value === undefined) {
73
+ var _journey;
74
+ (_journey = journey).timingWithheldReason || (_journey.timingWithheldReason = "missing_clock");
75
+ return;
76
+ }
77
+ journey.checkpoints[checkpoint] = value;
78
+ } catch (unused) {
79
+ // Telemetry cannot affect product behavior.
80
+ }
81
+ }
82
+ observeResponse(observation) {
83
+ try {
84
+ var // Compatible observations may enrich missing identity without moving the first-response clock.
85
+ _journey, _expectedResultId;
86
+ if (observation.kind === "ignore") return;
87
+ if (observation.journeyKind !== "button" && observation.journeyKind !== "smart_form") return;
88
+ if (observation.kind !== "terminal" && observation.kind !== "result") return;
89
+ // Runtime callers can bypass TypeScript; only renderer readiness can report success.
90
+ if (observation.kind === "terminal" && !isJourneyNonSuccessOutcome(observation.outcome)) {
91
+ return;
92
+ }
93
+ if (observation.kind === "result" && (!isJourneyReadyResult(observation.resultType) || observation.resultId !== undefined && !isJourneyResultId(observation.resultId) || observation.correlationQuality !== undefined && observation.correlationQuality !== "ambiguous")) return;
94
+ const journey = observation.journeyId ? this.activeById.get(observation.journeyId) : undefined;
95
+ if (!journey || journey.start.journeyKind !== observation.journeyKind) {
96
+ this.emitDiagnostic(_extends({
97
+ journeyKind: observation.journeyKind,
98
+ observationType: observation.kind === "terminal" ? "terminal" : "response",
99
+ attributionFailureReason: !observation.journeyId ? "missing_journey_id" : !isJourneyId(observation.journeyId) ? "malformed_journey_id" : !journey ? "unknown_journey_id" : "journey_kind_mismatch",
100
+ receivedJourneyId: observation.journeyId
101
+ }, observation.kind === "terminal" ? {
102
+ outcome: observation.outcome
103
+ } : {
104
+ resultType: observation.resultType
105
+ }));
106
+ return;
107
+ }
108
+ if (observation.kind === "terminal") {
109
+ this.finish(journey, observation.outcome);
110
+ return;
111
+ }
112
+ if (journey.expectedResultType && (journey.expectedResultType !== observation.resultType || journey.expectedResultId !== undefined && observation.resultId !== undefined && journey.expectedResultId !== observation.resultId)) {
113
+ this.emitDiagnostic({
114
+ journeyKind: observation.journeyKind,
115
+ observationType: "response",
116
+ attributionFailureReason: "result_identity_mismatch",
117
+ receivedJourneyId: observation.journeyId,
118
+ resultType: observation.resultType
119
+ });
120
+ return;
121
+ }
122
+ if (observation.correlationQuality) {
123
+ var _journey1;
124
+ journey.correlationQuality = observation.correlationQuality;
125
+ (_journey1 = journey).timingWithheldReason || (_journey1.timingWithheldReason = observation.correlationQuality);
126
+ }
127
+ if (!journey.expectedResultType) {
128
+ journey.expectedResultType = observation.resultType;
129
+ this.recordCheckpoint(journey, "result_received");
130
+ }
131
+ (_expectedResultId = (_journey = journey).expectedResultId) != null ? _expectedResultId : _journey.expectedResultId = observation.resultId;
132
+ } catch (unused) {
133
+ // Telemetry cannot affect product behavior.
134
+ }
135
+ }
136
+ markReady(input) {
137
+ try {
138
+ if (input.journeyKind !== "button" && input.journeyKind !== "smart_form" || !isJourneyReadyResult(input.resultType) || !isJourneyUiSurface(input.uiSurface) || !isJourneyResultId(input.resultId) || !isJourneyBookerDetail(input.bookerResultDetail)) return;
139
+ const journey = input.journeyId ? this.activeById.get(input.journeyId) : undefined;
140
+ if (!journey || journey.start.journeyKind !== input.journeyKind || journey.expectedResultType && (journey.expectedResultType !== input.resultType || journey.expectedResultId !== undefined && journey.expectedResultId !== input.resultId)) {
141
+ this.emitDiagnostic({
142
+ journeyKind: input.journeyKind,
143
+ observationType: "renderer_ready",
144
+ attributionFailureReason: !input.journeyId ? "missing_journey_id" : !isJourneyId(input.journeyId) ? "malformed_journey_id" : !journey ? "unknown_journey_id" : journey.start.journeyKind !== input.journeyKind ? "journey_kind_mismatch" : "result_identity_mismatch",
145
+ receivedJourneyId: input.journeyId,
146
+ resultType: input.resultType,
147
+ uiSurface: input.uiSurface,
148
+ bookerResultDetail: input.bookerResultDetail
149
+ });
150
+ return;
151
+ }
152
+ this.finish(journey, "success", input.resultType, {
153
+ uiSurface: input.uiSurface,
154
+ bookerResultDetail: input.bookerResultDetail
155
+ });
156
+ } catch (unused) {
157
+ // Telemetry cannot affect product behavior.
158
+ }
159
+ }
160
+ terminate(journeyId, outcome, resultType) {
161
+ try {
162
+ if (!isJourneyNonSuccessOutcome(outcome)) return;
163
+ if (resultType !== undefined && !isJourneyResultType(resultType)) return;
164
+ const journey = this.activeById.get(journeyId);
165
+ if (journey) this.finishLifecycle(journey, outcome, resultType);
166
+ } catch (unused) {
167
+ // Telemetry cannot affect product behavior.
168
+ }
169
+ }
170
+ dispose(options) {
171
+ try {
172
+ const active = [
173
+ ...this.activeById.values()
174
+ ];
175
+ if (options == null ? void 0 : options.finishActiveAs) {
176
+ for (const journey of active){
177
+ this.finishLifecycle(journey, options.finishActiveAs, undefined, true);
178
+ }
179
+ } else {
180
+ this.activeById.clear();
181
+ this.removeListeners();
182
+ }
183
+ } catch (unused) {
184
+ this.activeById.clear();
185
+ this.removeListeners();
186
+ }
187
+ }
188
+ recordCheckpoint(journey, checkpoint) {
189
+ if (journey.checkpoints[checkpoint] !== undefined) return;
190
+ const value = this.readNow();
191
+ if (value === undefined) {
192
+ var _journey;
193
+ (_journey = journey).timingWithheldReason || (_journey.timingWithheldReason = "missing_clock");
194
+ } else {
195
+ journey.checkpoints[checkpoint] = value;
196
+ }
197
+ }
198
+ emitDiagnostic(input) {
199
+ const candidates = [
200
+ ...this.activeById.values()
201
+ ].filter((journey)=>journey.start.journeyKind === input.journeyKind);
202
+ if (candidates.length === 0) return;
203
+ // Bound noise by finite observation/reason pairs, never by arbitrary received IDs.
204
+ // State lives only as long as the active attempts; ordinary and late traffic is ignored.
205
+ const diagnosticKey = input.observationType + ":" + input.attributionFailureReason;
206
+ if (candidates.every((journey)=>journey.diagnosticKeys.has(diagnosticKey))) return;
207
+ for (const journey of candidates)journey.diagnosticKeys.add(diagnosticKey);
208
+ const observedAtMs = this.readNow();
209
+ const candidate = this.selectDiagnosticCandidate(candidates, observedAtMs);
210
+ const attributionMethod = candidates.length === 0 ? "none" : candidates.length === 1 ? "single_active" : candidate ? "nearest_preceding" : "none";
211
+ const durationMs = candidate && !candidate.timingWithheldReason ? elapsed(candidate.startedAtMs, observedAtMs) : undefined;
212
+ const record = _extends({
213
+ journeyKind: input.journeyKind,
214
+ observationType: input.observationType,
215
+ attributionMethod,
216
+ attributionFailureReason: input.attributionFailureReason,
217
+ candidateCount: candidates.length
218
+ }, candidate ? {
219
+ candidateStart: candidate.start
220
+ } : {}, isJourneyId(input.receivedJourneyId) ? {
221
+ receivedJourneyId: input.receivedJourneyId
222
+ } : {}, input.resultType ? {
223
+ resultType: input.resultType
224
+ } : {}, input.outcome ? {
225
+ outcome: input.outcome
226
+ } : {}, input.uiSurface ? {
227
+ uiSurface: input.uiSurface
228
+ } : {}, input.bookerResultDetail ? {
229
+ bookerResultDetail: input.bookerResultDetail
230
+ } : {}, durationMs !== undefined ? {
231
+ durationMs
232
+ } : {});
233
+ try {
234
+ this.transport.emitDiagnostic(record);
235
+ } catch (unused) {
236
+ // Telemetry cannot affect product behavior.
237
+ }
238
+ }
239
+ selectDiagnosticCandidate(candidates, observedAtMs) {
240
+ if (candidates.length === 1) return candidates[0];
241
+ if (observedAtMs === undefined) return undefined;
242
+ return candidates.filter((candidate)=>candidate.startedAtMs !== undefined && candidate.startedAtMs <= observedAtMs).sort((left, right)=>{
243
+ var _right_startedAtMs, _left_startedAtMs;
244
+ return ((_right_startedAtMs = right.startedAtMs) != null ? _right_startedAtMs : 0) - ((_left_startedAtMs = left.startedAtMs) != null ? _left_startedAtMs : 0);
245
+ })[0];
246
+ }
247
+ finishLifecycle(journey, outcome, resultType, fireAndForget) {
248
+ if (fireAndForget === void 0) fireAndForget = false;
249
+ this.finish(journey, outcome, resultType, undefined, fireAndForget);
250
+ }
251
+ finish(journey, outcome, resultType, ready, fireAndForget) {
252
+ if (fireAndForget === void 0) fireAndForget = false;
253
+ var _journey_expectedResultType;
254
+ const { journeyId } = journey.start;
255
+ if (!this.activeById.has(journeyId)) return;
256
+ const finishedAtMs = this.readNow();
257
+ const timings = this.timings(journey, finishedAtMs, ready !== undefined);
258
+ const terminalResultType = (_journey_expectedResultType = journey.expectedResultType) != null ? _journey_expectedResultType : resultType;
259
+ const record = _extends({
260
+ start: journey.start,
261
+ outcome,
262
+ correlationQuality: journey.correlationQuality
263
+ }, terminalResultType ? {
264
+ resultType: terminalResultType
265
+ } : {}, (ready == null ? void 0 : ready.uiSurface) ? {
266
+ uiSurface: ready.uiSurface
267
+ } : {}, (ready == null ? void 0 : ready.bookerResultDetail) ? {
268
+ bookerResultDetail: ready.bookerResultDetail
269
+ } : {}, timings);
270
+ // Invalid runtime input must not consume an attempt before a valid callback can finish it.
271
+ if (!isValidJourneyTerminal(record)) return;
272
+ this.activeById.delete(journeyId);
273
+ if (this.activeById.size === 0) this.removeListeners();
274
+ try {
275
+ this.transport.emitTerminal(record, fireAndForget ? {
276
+ fireAndForget: true
277
+ } : undefined);
278
+ } catch (unused) {
279
+ // State is already terminal even when transport is unavailable.
280
+ }
281
+ }
282
+ timings(journey, finishedAtMs, rendererReady) {
283
+ // Construct a candidate record without mutating active state: validation may still reject it.
284
+ if (journey.timingWithheldReason) return {
285
+ timingWithheldReason: journey.timingWithheldReason
286
+ };
287
+ if (finishedAtMs === undefined) return {
288
+ timingWithheldReason: "missing_clock"
289
+ };
290
+ const { startedAtMs } = journey;
291
+ const request = journey.checkpoints.request_dispatched;
292
+ const response = journey.checkpoints.result_received;
293
+ const milestones = [
294
+ startedAtMs,
295
+ journey.checkpoints.provider_pending,
296
+ journey.checkpoints.provider_completed,
297
+ request,
298
+ journey.checkpoints.acknowledgement_received,
299
+ response,
300
+ journey.checkpoints.rendering,
301
+ finishedAtMs
302
+ ].filter((milestone)=>milestone !== undefined);
303
+ const hasInvalidOrder = milestones.some((milestone, index)=>{
304
+ const previous = milestones[index - 1];
305
+ return previous !== undefined && milestone < previous;
306
+ });
307
+ if (hasInvalidOrder) {
308
+ return {
309
+ timingWithheldReason: "invalid_clock"
310
+ };
311
+ }
312
+ const durationMs = elapsed(startedAtMs, finishedAtMs);
313
+ if (durationMs === undefined) {
314
+ return {
315
+ timingWithheldReason: "invalid_clock"
316
+ };
317
+ }
318
+ return _extends({
319
+ durationMs
320
+ }, elapsed(startedAtMs, request) !== undefined ? {
321
+ preRequestMs: elapsed(startedAtMs, request)
322
+ } : {}, elapsed(request, response) !== undefined ? {
323
+ requestToResponseMs: elapsed(request, response)
324
+ } : {}, rendererReady && elapsed(response, finishedAtMs) !== undefined ? {
325
+ responseToReadyMs: elapsed(response, finishedAtMs)
326
+ } : {});
327
+ }
328
+ readNow() {
329
+ try {
330
+ const value = this.now();
331
+ return Number.isFinite(value) ? value : undefined;
332
+ } catch (unused) {
333
+ return undefined;
334
+ }
335
+ }
336
+ isHidden() {
337
+ try {
338
+ return typeof document !== "undefined" && document.visibilityState === "hidden";
339
+ } catch (unused) {
340
+ return false;
341
+ }
342
+ }
343
+ ensureListeners() {
344
+ if (this.listening || typeof document === "undefined" || typeof window === "undefined") return;
345
+ __nativeDocumentAddEventListener("visibilitychange", this.handleVisibilityChange);
346
+ __nativeWindowAddEventListener("pagehide", this.handlePageHide);
347
+ this.listening = true;
348
+ }
349
+ removeListeners() {
350
+ if (!this.listening || typeof document === "undefined" || typeof window === "undefined") return;
351
+ __nativeDocumentRemoveEventListener("visibilitychange", this.handleVisibilityChange);
352
+ __nativeWindowRemoveEventListener("pagehide", this.handlePageHide);
353
+ this.listening = false;
354
+ }
355
+ constructor({ transport, now = ()=>performance.now(), createId = ()=>crypto.randomUUID() }){
356
+ this.activeById = new Map();
357
+ this.listening = false;
358
+ this.handleVisibilityChange = ()=>{
359
+ try {
360
+ if (!this.isHidden()) return;
361
+ for (const journey of this.activeById.values()){
362
+ var _journey;
363
+ (_journey = journey).timingWithheldReason || (_journey.timingWithheldReason = "backgrounded");
364
+ }
365
+ } catch (unused) {
366
+ // Telemetry cannot affect product behavior.
367
+ }
368
+ };
369
+ this.handlePageHide = (event)=>{
370
+ try {
371
+ if (event.persisted) return;
372
+ for (const journey of [
373
+ ...this.activeById.values()
374
+ ]){
375
+ this.finishLifecycle(journey, "abandoned", undefined, true);
376
+ }
377
+ } catch (unused) {
378
+ // Telemetry cannot affect product behavior.
379
+ }
380
+ };
381
+ this.transport = transport;
382
+ this.now = now;
383
+ this.createId = createId;
384
+ }
385
+ }
386
+
387
+ export { ClientExperienceJourneyTracker };
@@ -0,0 +1,104 @@
1
+ import { _extends } from "../../js/4101.js";
2
+ import { isValidJourneyTerminal, isValidJourneyStart } from "./schema.js";
3
+
4
+
5
+
6
+ const START_EVENT = "client_experience_journey_started";
7
+ const TERMINAL_EVENT = "client_experience_journey_terminal";
8
+ const DIAGNOSTIC_EVENT = "client_experience_journey_diagnostic";
9
+ function startExtra(start) {
10
+ return {
11
+ schema_version: (/* inlined export .JOURNEY_SCHEMA_VERSION */1),
12
+ journey_id: start.journeyId,
13
+ journey_kind: start.journeyKind,
14
+ entry_path: start.entryPath,
15
+ start_anchor: start.startAnchor,
16
+ navigation_mode: start.navigationMode
17
+ };
18
+ }
19
+ function terminalExtra(record) {
20
+ return _extends({}, startExtra(record.start), {
21
+ outcome: record.outcome,
22
+ correlation_quality: record.correlationQuality
23
+ }, record.resultType ? {
24
+ result_type: record.resultType
25
+ } : {}, record.uiSurface ? {
26
+ ui_surface: record.uiSurface
27
+ } : {}, record.bookerResultDetail ? {
28
+ booker_result_detail: record.bookerResultDetail
29
+ } : {}, record.durationMs !== undefined ? {
30
+ duration_ms: record.durationMs
31
+ } : {}, record.preRequestMs !== undefined ? {
32
+ pre_request_ms: record.preRequestMs
33
+ } : {}, record.requestToResponseMs !== undefined ? {
34
+ request_to_response_ms: record.requestToResponseMs
35
+ } : {}, record.responseToReadyMs !== undefined ? {
36
+ response_to_ready_ms: record.responseToReadyMs
37
+ } : {}, record.timingWithheldReason ? {
38
+ timing_withheld_reason: record.timingWithheldReason
39
+ } : {});
40
+ }
41
+ function diagnosticExtra(record) {
42
+ const candidate = record.candidateStart;
43
+ return _extends({
44
+ schema_version: (/* inlined export .JOURNEY_SCHEMA_VERSION */1),
45
+ journey_kind: record.journeyKind,
46
+ observation_type: record.observationType,
47
+ has_journey_id: false,
48
+ attribution_method: record.attributionMethod,
49
+ attribution_failure_reason: record.attributionFailureReason,
50
+ candidate_count: record.candidateCount,
51
+ metric_eligible: false
52
+ }, candidate ? {
53
+ candidate_journey_id: candidate.journeyId
54
+ } : {}, record.receivedJourneyId ? {
55
+ received_journey_id: record.receivedJourneyId
56
+ } : {}, candidate ? {
57
+ entry_path: candidate.entryPath,
58
+ start_anchor: candidate.startAnchor,
59
+ navigation_mode: candidate.navigationMode
60
+ } : {}, record.resultType ? {
61
+ result_type: record.resultType
62
+ } : {}, record.outcome ? {
63
+ outcome: record.outcome
64
+ } : {}, record.uiSurface ? {
65
+ ui_surface: record.uiSurface
66
+ } : {}, record.bookerResultDetail ? {
67
+ booker_result_detail: record.bookerResultDetail
68
+ } : {}, record.durationMs !== undefined ? {
69
+ duration_ms: record.durationMs
70
+ } : {});
71
+ }
72
+ function createJourneyTransport(param) {
73
+ let { log } = param;
74
+ return {
75
+ emitStart (start) {
76
+ if (!isValidJourneyStart(start)) return;
77
+ log({
78
+ type: "log",
79
+ message: START_EVENT,
80
+ extra: startExtra(start)
81
+ });
82
+ },
83
+ emitTerminal (record, options) {
84
+ if (!isValidJourneyTerminal(record)) return;
85
+ log(_extends({
86
+ type: "log",
87
+ message: TERMINAL_EVENT,
88
+ extra: terminalExtra(record)
89
+ }, (options == null ? void 0 : options.fireAndForget) ? {
90
+ fireAndForget: true,
91
+ flushImmediately: true
92
+ } : {}));
93
+ },
94
+ emitDiagnostic (record) {
95
+ log({
96
+ type: "log",
97
+ message: DIAGNOSTIC_EVENT,
98
+ extra: diagnosticExtra(record)
99
+ });
100
+ }
101
+ };
102
+ }
103
+
104
+ export { createJourneyTransport };
@@ -146,7 +146,7 @@
146
146
  askAnythingAbout: "Stellen Sie eine Frage zu {{company}}",
147
147
  weakConnection: "Schwache Verbindung. Neuverbindung wird hergestellt…",
148
148
  aiAssistantAvatar: "{{name}} Avatar",
149
- yourAssistant: "Ihr Assistent",
149
+ yourAssistant: "Ihrem Assistenten",
150
150
  videoMode: "Video",
151
151
  voiceMode: "Sprache",
152
152
  videoModeAriaLabel: "Videomodus",
@@ -72,7 +72,7 @@ class ServerLogger {
72
72
  * // Fire-and-forget for page unload
73
73
  * serverLog({ message: "Page closing", fireAndForget: true });
74
74
  */ serverLog(param) {
75
- let { type = "log", message: event, extra = {}, fireAndForget = false } = param;
75
+ let { type = "log", message: event, extra = {}, fireAndForget = false, flushImmediately = false } = param;
76
76
  var _class_private_field_loose_base__config_featureFlags;
77
77
  if (!_class_private_field_loose_base(this, _config)[_config].host || !_class_private_field_loose_base(this, _config)[_config].token) {
78
78
  return;
@@ -84,6 +84,9 @@ class ServerLogger {
84
84
  extra,
85
85
  fireAndForget
86
86
  });
87
+ if (flushImmediately) this.flushBatch({
88
+ fireAndForget
89
+ });
87
90
  return;
88
91
  }
89
92
  this.sendEvent("visitor_events", {
package/widget/tracker.js CHANGED
@@ -14,12 +14,14 @@ import { default as data360_visitor } from "./data360_visitor.js";
14
14
  import { default as eloqua_visitor } from "./eloqua_visitor.js";
15
15
  import { armFormCtaBookerIntent } from "./form_cta_booker_intent.js";
16
16
  import { default as hubspot_visitor } from "./hubspot_visitor.js";
17
+ import { ClientExperienceJourneyTracker } from "./journey/tracker.js";
17
18
  import { default as marketo_visitor } from "./marketo_visitor.js";
18
19
  import { ButtonsFormsTriggeringTracer } from "./meetings/tracing/buttons_forms_triggering_tracer.js";
19
20
  import { default as pardot_visitor } from "./pardot_visitor.js";
20
21
  import { default as personalizations_config } from "./personalizations_config.js";
21
22
  import { default as smart_website_config } from "./smart_website_config.js";
22
23
  import "./telemetry/index.js";
24
+ import { createJourneyTransport } from "./journey/transport.js";
23
25
  import { inject } from "../src/mixins/injectable.js";
24
26
  import { ignoreIfDisposed } from "../src/mixins/disposable.js";
25
27
  import { telemetry, withTelemetryCtx, getTelemetryCtx } from "./telemetry/telemetry.js";
@@ -54,6 +56,8 @@ var throttle_default = /*#__PURE__*/__webpack_require__.n(throttle);
54
56
 
55
57
 
56
58
 
59
+
60
+
57
61
 
58
62
 
59
63
 
@@ -852,6 +856,18 @@ class Tracker extends WidgetObject {
852
856
  // AI Nudge
853
857
  this.timerStartedAt = null;
854
858
  this.timerTimeOnSite = 0;
859
+ this.clientExperienceJourneys = this.featureFlags.clientExperienceJourneyTelemetry ? new ClientExperienceJourneyTracker({
860
+ createId: esm_browser_v4,
861
+ transport: createJourneyTransport({
862
+ log: (params)=>this.widget.serverLog(params)
863
+ })
864
+ }) : undefined;
865
+ this.callOnDispose(()=>{
866
+ var _this_clientExperienceJourneys;
867
+ return (_this_clientExperienceJourneys = this.clientExperienceJourneys) == null ? void 0 : _this_clientExperienceJourneys.dispose({
868
+ finishActiveAs: "superseded"
869
+ });
870
+ });
855
871
  if (this.config.smartCtaTriggers) {
856
872
  this.smartWebsiteConfig = new smart_website_config({
857
873
  smartCtaTriggers: this.config.smartCtaTriggers
@@ -947,14 +947,16 @@ class Widget extends Injectable(Disposable(Object)) {
947
947
  console.warn("Error showing debug banner!");
948
948
  }
949
949
  }
950
- const _this = this;
951
950
  if (typeof window.MktoForms2 !== "undefined") {
952
951
  this.debugState.logEvent("widget:marketo:MktoForms2Found", "success");
953
952
  this.initMarketoForms();
954
953
  } else if (Object.hasOwn(window, "MktoForms2")) {
955
- // NO-OP: Property exists - our trap is already set up from previous initialization, prevents "Cannot redefine property" errors.
954
+ // The trap is already installed from a previous initialization. Update the dispatch target
955
+ // so the setter calls the current widget instead of a potentially disposed one.
956
+ window.__qualifiedMarketoTarget = this;
956
957
  } else {
957
958
  this.debugState.logEvent("widget:marketo:MktoForms2Found", "error");
959
+ window.__qualifiedMarketoTarget = this;
958
960
  let _MktoForms2;
959
961
  Object.defineProperty(window, "MktoForms2", {
960
962
  get () {
@@ -962,11 +964,12 @@ class Widget extends Injectable(Disposable(Object)) {
962
964
  },
963
965
  set (value) {
964
966
  _MktoForms2 = value;
967
+ const target = window.__qualifiedMarketoTarget;
965
968
  widget_setTimeout(()=>{
966
969
  try {
967
- _this.initMarketoForms();
970
+ target == null ? void 0 : target.initMarketoForms();
968
971
  } catch (error) {
969
- _this.debugState.logEvent("widget:marketo:initMarketoFormsComplete", "error", {
972
+ target == null ? void 0 : target.debugState.logEvent("widget:marketo:initMarketoFormsComplete", "error", {
970
973
  error
971
974
  });
972
975
  }
@@ -989,9 +992,12 @@ class Widget extends Injectable(Disposable(Object)) {
989
992
  this.debugState.logEvent("widget:hubspot:hbsptFound", "success");
990
993
  this.initHubspotForms(contentWindow);
991
994
  } else if (Object.hasOwn(contentWindow, "hbspt")) {
992
- // NO-OP: Property exists - our trap is already set up from previous initialization, prevents "Cannot redefine property" errors.
995
+ // The trap is already installed. Update the dispatch target so the setter calls the
996
+ // current widget instead of a potentially disposed one.
997
+ contentWindow.__qualifiedHubspotTarget = this;
993
998
  } else {
994
999
  this.debugState.logEvent("widget:hubspot:hbsptFound", "error");
1000
+ contentWindow.__qualifiedHubspotTarget = this;
995
1001
  let _hbspt;
996
1002
  Object.defineProperty(contentWindow, "hbspt", {
997
1003
  get () {
@@ -999,11 +1005,12 @@ class Widget extends Injectable(Disposable(Object)) {
999
1005
  },
1000
1006
  set (value) {
1001
1007
  _hbspt = value;
1008
+ const target = contentWindow.__qualifiedHubspotTarget;
1002
1009
  widget_setTimeout(()=>{
1003
1010
  try {
1004
- _this.initHubspotForms(contentWindow);
1011
+ target == null ? void 0 : target.initHubspotForms(contentWindow);
1005
1012
  } catch (error) {
1006
- _this.debugState.logEvent("widget:hubspot:initHubspotFormsComplete", "error", {
1013
+ target == null ? void 0 : target.debugState.logEvent("widget:hubspot:initHubspotFormsComplete", "error", {
1007
1014
  error
1008
1015
  });
1009
1016
  }
@@ -1034,15 +1041,15 @@ class Widget extends Injectable(Disposable(Object)) {
1034
1041
  if (typeof contentWindow.HubSpotFormsV4 !== "undefined") {
1035
1042
  widget_clearInterval(pollId);
1036
1043
  try {
1037
- _this.initHubspotFormsV4(contentWindow);
1044
+ this.initHubspotFormsV4(contentWindow);
1038
1045
  } catch (error) {
1039
- _this.debugState.logEvent("widget:hubspotV4:initComplete", "error", {
1046
+ this.debugState.logEvent("widget:hubspotV4:initComplete", "error", {
1040
1047
  error
1041
1048
  });
1042
1049
  }
1043
1050
  } else if (elapsed >= MAX_POLL_TIME_MS) {
1044
1051
  widget_clearInterval(pollId);
1045
- _this.debugState.logEvent("widget:hubspotV4:pollTimeout", "error");
1052
+ this.debugState.logEvent("widget:hubspotV4:pollTimeout", "error");
1046
1053
  }
1047
1054
  }, POLL_INTERVAL_MS);
1048
1055
  this.hubspotV4PollIds.push(pollId);
@@ -1081,12 +1088,17 @@ class Widget extends Injectable(Disposable(Object)) {
1081
1088
  this.debugState.logEvent("widget:marketo:MktoForms2Found", "error", {}, '"window.MktoForms" was previously defined, but is no longer present. This website may have executed javascript which is interfering with the Marketo Smart Form initialization process.');
1082
1089
  return;
1083
1090
  }
1091
+ // Marketo has no API to unregister whenReady/onSubmit/onSuccess callbacks, so after a teardown
1092
+ // they outlive this instance (and whenReady replays for forms that are already ready). Each
1093
+ // callback bails once this instance is disposed so only the live widget reacts to the form.
1084
1094
  window.MktoForms2.whenReady((mktoForm)=>{
1095
+ if (this.isDisposed) return;
1085
1096
  if (!this.tracker.config.formBotTriggers.marketo.includes(String(mktoForm.getId()))) return;
1086
1097
  this.debugState.logEvent("widget:marketo:configuredFormFound", "success", {
1087
1098
  formId: mktoForm.getId()
1088
1099
  });
1089
1100
  mktoForm.onSubmit((form)=>{
1101
+ if (this.isDisposed) return;
1090
1102
  this.initMarketoFormsRedirectListeners();
1091
1103
  const values = form.getValues();
1092
1104
  const formId = values.formid;
@@ -1100,6 +1112,7 @@ class Widget extends Injectable(Disposable(Object)) {
1100
1112
  });
1101
1113
  });
1102
1114
  mktoForm.onSuccess((values, followUpUrl)=>{
1115
+ if (this.isDisposed) return;
1103
1116
  this.marketoFormSucceededAt = Date.now();
1104
1117
  const formId = values.formid;
1105
1118
  const formEmail = values.Email;
@@ -1121,7 +1134,7 @@ class Widget extends Injectable(Disposable(Object)) {
1121
1134
  formId
1122
1135
  });
1123
1136
  widget_setTimeout(()=>{
1124
- if (this.marketoFormRedirecting) return;
1137
+ if (this.isDisposed || this.marketoFormRedirecting) return;
1125
1138
  this.debugState.logEvent("widget:marketo:showExperienceForForm", "success", {
1126
1139
  formId
1127
1140
  });
@@ -1191,14 +1204,17 @@ class Widget extends Injectable(Disposable(Object)) {
1191
1204
  var _event_data;
1192
1205
  if (event.origin !== window.location.origin || ((_event_data = event.data) == null ? void 0 : _event_data.type) !== "hsFormCallback" || !this.tracker.config.formBotTriggers.hubspot.includes(event.data.id)) return;
1193
1206
  if (event.data.eventName === "onFormSubmit") {
1207
+ var _ref;
1208
+ var _email_;
1194
1209
  const email = event.data.data.filter((field)=>field.name === "email");
1195
1210
  values = event.data.data.reduce((acc, field)=>{
1196
1211
  acc[field.name] = field.value;
1197
1212
  return acc;
1198
1213
  }, {});
1199
1214
  const lengthCheckedValues = this.excludeLongFieldValue(values, "hubspot");
1200
- if (email) {
1201
- formEmail = email[0].value;
1215
+ // Reset per submission so onFormSubmitted never reuses an earlier submission's email.
1216
+ formEmail = (_ref = (_email_ = email[0]) == null ? void 0 : _email_.value) != null ? _ref : null;
1217
+ if (formEmail) {
1202
1218
  this.visitor.setFormExperienceDataWithExpiry({
1203
1219
  formType: "hubspot",
1204
1220
  formData: _extends({}, lengthCheckedValues, {