diff --git a/.size-limit.js b/.size-limit.js index 6e005fd7c3e7..23ad7b0974ed 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -52,35 +52,28 @@ module.exports = [ path: 'packages/browser/build/npm/esm/index.js', import: createImport('init', 'browserTracingIntegration', 'replayIntegration', 'feedbackIntegration'), gzip: true, - limit: '83 KB', + limit: '86 KB', }, { name: '@sentry/browser (incl. Feedback)', path: 'packages/browser/build/npm/esm/index.js', import: createImport('init', 'feedbackIntegration'), gzip: true, - limit: '37 KB', - }, - { - name: '@sentry/browser (incl. Feedback, Feedback Modal)', - path: 'packages/browser/build/npm/esm/index.js', - import: createImport('init', 'feedbackIntegration', 'feedbackModalIntegration'), - gzip: true, - limit: '37 KB', + limit: '40 KB', }, { - name: '@sentry/browser (incl. Feedback, Feedback Modal, Feedback Screenshot)', + name: '@sentry/browser (incl. sendFeedback)', path: 'packages/browser/build/npm/esm/index.js', - import: createImport('init', 'feedbackIntegration', 'feedbackModalIntegration', 'feedbackScreenshotIntegration'), + import: createImport('init', 'sendFeedback'), gzip: true, - limit: '40 KB', + limit: '28 KB', }, { - name: '@sentry/browser (incl. sendFeedback)', + name: '@sentry/browser (incl. FeedbackAsync)', path: 'packages/browser/build/npm/esm/index.js', - import: createImport('init', 'sendFeedback'), + import: createImport('init', 'feedbackAsyncIntegration'), gzip: true, - limit: '30 KB', + limit: '33 KB', }, // React SDK (ESM) { @@ -167,6 +160,13 @@ module.exports = [ brotli: false, limit: '220 KB', }, + { + name: 'CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed', + path: createCDNPath('bundle.tracing.replay.feedback.min.js'), + gzip: false, + brotli: false, + limit: '261 KB', + }, // Next.js SDK (ESM) { name: '@sentry/nextjs (client)', diff --git a/packages/browser/src/feedback.ts b/packages/browser/src/feedback.ts new file mode 100644 index 000000000000..20f3be3f4fbe --- /dev/null +++ b/packages/browser/src/feedback.ts @@ -0,0 +1,13 @@ +import { + buildFeedbackIntegration, + feedbackModalIntegration, + feedbackScreenshotIntegration, +} from '@sentry-internal/feedback'; +import { lazyLoadIntegration } from './utils/lazyLoadIntegration'; + +// The full feedback widget, with everything pre-loaded +export const feedbackIntegration = buildFeedbackIntegration({ + lazyLoadIntegration, + getModalIntegration: () => feedbackModalIntegration, + getScreenshotIntegration: () => feedbackScreenshotIntegration, +}); diff --git a/packages/browser/src/feedbackAsync.ts b/packages/browser/src/feedbackAsync.ts new file mode 100644 index 000000000000..a4b518597357 --- /dev/null +++ b/packages/browser/src/feedbackAsync.ts @@ -0,0 +1,7 @@ +import { buildFeedbackIntegration } from '@sentry-internal/feedback'; +import { lazyLoadIntegration } from './utils/lazyLoadIntegration'; + +// This is for users who want to have a lazy-loaded feedback widget +export const feedbackAsyncIntegration = buildFeedbackIntegration({ + lazyLoadIntegration, +}); diff --git a/packages/browser/src/index.bundle.feedback.ts b/packages/browser/src/index.bundle.feedback.ts index 850da7bf1e26..eacbd0fb89c4 100644 --- a/packages/browser/src/index.bundle.feedback.ts +++ b/packages/browser/src/index.bundle.feedback.ts @@ -2,11 +2,7 @@ import { browserTracingIntegrationShim, replayIntegrationShim } from '@sentry-in export * from './index.bundle.base'; -export { - feedbackIntegration, - feedbackModalIntegration, - feedbackScreenshotIntegration, - getFeedback, -} from '@sentry-internal/feedback'; +export { feedbackIntegration } from './feedback'; +export { getFeedback } from '@sentry-internal/feedback'; export { browserTracingIntegrationShim as browserTracingIntegration, replayIntegrationShim as replayIntegration }; diff --git a/packages/browser/src/index.bundle.replay.ts b/packages/browser/src/index.bundle.replay.ts index 3a26a2db77ac..c2d267a5227d 100644 --- a/packages/browser/src/index.bundle.replay.ts +++ b/packages/browser/src/index.bundle.replay.ts @@ -1,17 +1,7 @@ -import { - browserTracingIntegrationShim, - feedbackIntegrationShim, - feedbackModalIntegrationShim, - feedbackScreenshotIntegrationShim, -} from '@sentry-internal/integration-shims'; +import { browserTracingIntegrationShim, feedbackIntegrationShim } from '@sentry-internal/integration-shims'; export * from './index.bundle.base'; export { replayIntegration } from '@sentry-internal/replay'; -export { - browserTracingIntegrationShim as browserTracingIntegration, - feedbackIntegrationShim as feedbackIntegration, - feedbackModalIntegrationShim as feedbackModalIntegration, - feedbackScreenshotIntegrationShim as feedbackScreenshotIntegration, -}; +export { browserTracingIntegrationShim as browserTracingIntegration, feedbackIntegrationShim as feedbackIntegration }; diff --git a/packages/browser/src/index.bundle.tracing.replay.feedback.ts b/packages/browser/src/index.bundle.tracing.replay.feedback.ts index 4c9c1e716534..8e31dba14027 100644 --- a/packages/browser/src/index.bundle.tracing.replay.feedback.ts +++ b/packages/browser/src/index.bundle.tracing.replay.feedback.ts @@ -15,12 +15,8 @@ export { setMeasurement, } from '@sentry/core'; -export { - feedbackIntegration, - feedbackModalIntegration, - feedbackScreenshotIntegration, - getFeedback, -} from '@sentry-internal/feedback'; +export { feedbackIntegration } from './feedback'; +export { getFeedback } from '@sentry-internal/feedback'; export { browserTracingIntegration, diff --git a/packages/browser/src/index.bundle.tracing.replay.ts b/packages/browser/src/index.bundle.tracing.replay.ts index 1b986f9e794a..f103fa297ace 100644 --- a/packages/browser/src/index.bundle.tracing.replay.ts +++ b/packages/browser/src/index.bundle.tracing.replay.ts @@ -1,8 +1,4 @@ -import { - feedbackIntegrationShim, - feedbackModalIntegrationShim, - feedbackScreenshotIntegrationShim, -} from '@sentry-internal/integration-shims'; +import { feedbackIntegrationShim } from '@sentry-internal/integration-shims'; import { registerSpanErrorInstrumentation } from '@sentry/core'; registerSpanErrorInstrumentation(); @@ -26,10 +22,6 @@ export { startBrowserTracingPageLoadSpan, } from './tracing/browserTracingIntegration'; -export { - feedbackIntegrationShim as feedbackIntegration, - feedbackModalIntegrationShim as feedbackModalIntegration, - feedbackScreenshotIntegrationShim as feedbackScreenshotIntegration, -}; +export { feedbackIntegrationShim as feedbackIntegration }; export { replayIntegration } from '@sentry-internal/replay'; diff --git a/packages/browser/src/index.bundle.tracing.ts b/packages/browser/src/index.bundle.tracing.ts index f956bc4db1f6..9af45dfa8572 100644 --- a/packages/browser/src/index.bundle.tracing.ts +++ b/packages/browser/src/index.bundle.tracing.ts @@ -1,9 +1,4 @@ -import { - feedbackIntegrationShim, - feedbackModalIntegrationShim, - feedbackScreenshotIntegrationShim, - replayIntegrationShim, -} from '@sentry-internal/integration-shims'; +import { feedbackIntegrationShim, replayIntegrationShim } from '@sentry-internal/integration-shims'; import { registerSpanErrorInstrumentation } from '@sentry/core'; registerSpanErrorInstrumentation(); @@ -27,9 +22,4 @@ export { startBrowserTracingPageLoadSpan, } from './tracing/browserTracingIntegration'; -export { - feedbackIntegrationShim as feedbackIntegration, - feedbackModalIntegrationShim as feedbackModalIntegration, - feedbackScreenshotIntegrationShim as feedbackScreenshotIntegration, - replayIntegrationShim as replayIntegration, -}; +export { feedbackIntegrationShim as feedbackIntegration, replayIntegrationShim as replayIntegration }; diff --git a/packages/browser/src/index.bundle.ts b/packages/browser/src/index.bundle.ts index 9433e3605c3b..f24e98b4f6db 100644 --- a/packages/browser/src/index.bundle.ts +++ b/packages/browser/src/index.bundle.ts @@ -1,8 +1,6 @@ import { browserTracingIntegrationShim, feedbackIntegrationShim, - feedbackModalIntegrationShim, - feedbackScreenshotIntegrationShim, replayIntegrationShim, } from '@sentry-internal/integration-shims'; @@ -11,7 +9,5 @@ export * from './index.bundle.base'; export { browserTracingIntegrationShim as browserTracingIntegration, feedbackIntegrationShim as feedbackIntegration, - feedbackModalIntegrationShim as feedbackModalIntegration, - feedbackScreenshotIntegrationShim as feedbackScreenshotIntegration, replayIntegrationShim as replayIntegration, }; diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 455214f60816..d5e905ff568c 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -30,10 +30,9 @@ export type { export { replayCanvasIntegration } from '@sentry-internal/replay-canvas'; +export { feedbackIntegration } from './feedback'; +export { feedbackAsyncIntegration } from './feedbackAsync'; export { - feedbackIntegration, - feedbackModalIntegration, - feedbackScreenshotIntegration, getFeedback, sendFeedback, } from '@sentry-internal/feedback'; diff --git a/packages/browser/test/unit/index.bundle.feedback.test.ts b/packages/browser/test/unit/index.bundle.feedback.test.ts index c403144d4306..bd231a95fe05 100644 --- a/packages/browser/test/unit/index.bundle.feedback.test.ts +++ b/packages/browser/test/unit/index.bundle.feedback.test.ts @@ -1,13 +1,12 @@ -import { replayIntegrationShim } from '@sentry-internal/integration-shims'; -import { feedbackIntegration, feedbackModalIntegration, feedbackScreenshotIntegration } from '@sentry/browser'; +import { browserTracingIntegrationShim, replayIntegrationShim } from '@sentry-internal/integration-shims'; +import { feedbackIntegration } from '../../src'; import * as FeedbackBundle from '../../src/index.bundle.feedback'; describe('index.bundle.feedback', () => { it('has correct exports', () => { + expect(FeedbackBundle.browserTracingIntegration).toBe(browserTracingIntegrationShim); expect(FeedbackBundle.replayIntegration).toBe(replayIntegrationShim); expect(FeedbackBundle.feedbackIntegration).toBe(feedbackIntegration); - expect(FeedbackBundle.feedbackModalIntegration).toBe(feedbackModalIntegration); - expect(FeedbackBundle.feedbackScreenshotIntegration).toBe(feedbackScreenshotIntegration); }); }); diff --git a/packages/browser/test/unit/index.bundle.replay.test.ts b/packages/browser/test/unit/index.bundle.replay.test.ts index 4e3d4e344734..3fec5d2bb6ab 100644 --- a/packages/browser/test/unit/index.bundle.replay.test.ts +++ b/packages/browser/test/unit/index.bundle.replay.test.ts @@ -1,8 +1,4 @@ -import { - feedbackIntegrationShim, - feedbackModalIntegrationShim, - feedbackScreenshotIntegrationShim, -} from '@sentry-internal/integration-shims'; +import { feedbackIntegrationShim } from '@sentry-internal/integration-shims'; import { replayIntegration } from '@sentry/browser'; import * as ReplayBundle from '../../src/index.bundle.replay'; @@ -11,7 +7,5 @@ describe('index.bundle.replay', () => { it('has correct exports', () => { expect(ReplayBundle.replayIntegration).toBe(replayIntegration); expect(ReplayBundle.feedbackIntegration).toBe(feedbackIntegrationShim); - expect(ReplayBundle.feedbackModalIntegration).toBe(feedbackModalIntegrationShim); - expect(ReplayBundle.feedbackScreenshotIntegration).toBe(feedbackScreenshotIntegrationShim); }); }); diff --git a/packages/browser/test/unit/index.bundle.test.ts b/packages/browser/test/unit/index.bundle.test.ts index f02e2528f802..1d459ddfd731 100644 --- a/packages/browser/test/unit/index.bundle.test.ts +++ b/packages/browser/test/unit/index.bundle.test.ts @@ -1,9 +1,4 @@ -import { - feedbackIntegrationShim, - feedbackModalIntegrationShim, - feedbackScreenshotIntegrationShim, - replayIntegrationShim, -} from '@sentry-internal/integration-shims'; +import { feedbackIntegrationShim, replayIntegrationShim } from '@sentry-internal/integration-shims'; import * as Bundle from '../../src/index.bundle'; @@ -11,7 +6,5 @@ describe('index.bundle', () => { it('has correct exports', () => { expect(Bundle.replayIntegration).toBe(replayIntegrationShim); expect(Bundle.feedbackIntegration).toBe(feedbackIntegrationShim); - expect(Bundle.feedbackModalIntegration).toBe(feedbackModalIntegrationShim); - expect(Bundle.feedbackScreenshotIntegration).toBe(feedbackScreenshotIntegrationShim); }); }); diff --git a/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts b/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts index 847f610ca702..72418b639241 100644 --- a/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts +++ b/packages/browser/test/unit/index.bundle.tracing.replay.feedback.test.ts @@ -1,10 +1,4 @@ -import { - browserTracingIntegration, - feedbackIntegration, - feedbackModalIntegration, - feedbackScreenshotIntegration, - replayIntegration, -} from '../../src'; +import { browserTracingIntegration, feedbackIntegration, replayIntegration } from '../../src'; import * as TracingReplayFeedbackBundle from '../../src/index.bundle.tracing.replay.feedback'; describe('index.bundle.tracing.replay.feedback', () => { @@ -12,7 +6,5 @@ describe('index.bundle.tracing.replay.feedback', () => { expect(TracingReplayFeedbackBundle.replayIntegration).toBe(replayIntegration); expect(TracingReplayFeedbackBundle.browserTracingIntegration).toBe(browserTracingIntegration); expect(TracingReplayFeedbackBundle.feedbackIntegration).toBe(feedbackIntegration); - expect(TracingReplayFeedbackBundle.feedbackModalIntegration).toBe(feedbackModalIntegration); - expect(TracingReplayFeedbackBundle.feedbackScreenshotIntegration).toBe(feedbackScreenshotIntegration); }); }); diff --git a/packages/browser/test/unit/index.bundle.tracing.replay.test.ts b/packages/browser/test/unit/index.bundle.tracing.replay.test.ts index f0228e575e68..5f5f1e649951 100644 --- a/packages/browser/test/unit/index.bundle.tracing.replay.test.ts +++ b/packages/browser/test/unit/index.bundle.tracing.replay.test.ts @@ -1,8 +1,4 @@ -import { - feedbackIntegrationShim, - feedbackModalIntegrationShim, - feedbackScreenshotIntegrationShim, -} from '@sentry-internal/integration-shims'; +import { feedbackIntegrationShim } from '@sentry-internal/integration-shims'; import { browserTracingIntegration, replayIntegration } from '../../src'; import * as TracingReplayBundle from '../../src/index.bundle.tracing.replay'; @@ -14,7 +10,5 @@ describe('index.bundle.tracing.replay', () => { expect(TracingReplayBundle.browserTracingIntegration).toBe(browserTracingIntegration); expect(TracingReplayBundle.feedbackIntegration).toBe(feedbackIntegrationShim); - expect(TracingReplayBundle.feedbackModalIntegration).toBe(feedbackModalIntegrationShim); - expect(TracingReplayBundle.feedbackScreenshotIntegration).toBe(feedbackScreenshotIntegrationShim); }); }); diff --git a/packages/browser/test/unit/index.bundle.tracing.test.ts b/packages/browser/test/unit/index.bundle.tracing.test.ts index 67a530b0e376..065654e054b9 100644 --- a/packages/browser/test/unit/index.bundle.tracing.test.ts +++ b/packages/browser/test/unit/index.bundle.tracing.test.ts @@ -1,9 +1,4 @@ -import { - feedbackIntegrationShim, - feedbackModalIntegrationShim, - feedbackScreenshotIntegrationShim, - replayIntegrationShim, -} from '@sentry-internal/integration-shims'; +import { feedbackIntegrationShim, replayIntegrationShim } from '@sentry-internal/integration-shims'; import { browserTracingIntegration } from '../../src'; import * as TracingBundle from '../../src/index.bundle.tracing'; @@ -13,7 +8,5 @@ describe('index.bundle.tracing', () => { expect(TracingBundle.replayIntegration).toBe(replayIntegrationShim); expect(TracingBundle.browserTracingIntegration).toBe(browserTracingIntegration); expect(TracingBundle.feedbackIntegration).toBe(feedbackIntegrationShim); - expect(TracingBundle.feedbackModalIntegration).toBe(feedbackModalIntegrationShim); - expect(TracingBundle.feedbackScreenshotIntegration).toBe(feedbackScreenshotIntegrationShim); }); }); diff --git a/packages/feedback/rollup.bundle.config.mjs b/packages/feedback/rollup.bundle.config.mjs index 6bdaefed8748..4d5620c4c040 100644 --- a/packages/feedback/rollup.bundle.config.mjs +++ b/packages/feedback/rollup.bundle.config.mjs @@ -4,7 +4,7 @@ export default [ ...makeBundleConfigVariants( makeBaseBundleConfig({ bundleType: 'addon', - entrypoints: ['src/index.bundle.ts'], + entrypoints: ['src/index.ts'], jsVersion: 'es6', licenseTitle: '@sentry-internal/feedback', outputFileBase: () => 'bundles/feedback', diff --git a/packages/feedback/src/core/getFeedback.test.ts b/packages/feedback/src/core/getFeedback.test.ts index f0cc7b70d0bc..58c5c0f8e88f 100644 --- a/packages/feedback/src/core/getFeedback.test.ts +++ b/packages/feedback/src/core/getFeedback.test.ts @@ -1,6 +1,6 @@ import { getCurrentScope } from '@sentry/core'; import { getFeedback } from './getFeedback'; -import { feedbackIntegration } from './integration'; +import { buildFeedbackIntegration } from './integration'; import { mockSdk } from './mockSdk'; describe('getFeedback', () => { @@ -25,16 +25,19 @@ describe('getFeedback', () => { }); it('works with a client with Feedback', () => { - const feedback = feedbackIntegration(); + const feedbackIntegration = buildFeedbackIntegration({ + lazyLoadIntegration: jest.fn(), + }); + const configuredIntegration = feedbackIntegration({}); mockSdk({ sentryOptions: { - integrations: [feedback], + integrations: [configuredIntegration], }, }); const actual = getFeedback(); expect(actual).toBeDefined(); - expect(actual === feedback).toBe(true); + expect(actual === configuredIntegration).toBe(true); }); }); diff --git a/packages/feedback/src/core/getFeedback.ts b/packages/feedback/src/core/getFeedback.ts index f729d308971c..120c9ccd1d43 100644 --- a/packages/feedback/src/core/getFeedback.ts +++ b/packages/feedback/src/core/getFeedback.ts @@ -1,10 +1,12 @@ import { getClient } from '@sentry/core'; -import type { feedbackIntegration } from './integration'; +import type { buildFeedbackIntegration } from './integration'; + +type FeedbackIntegration = ReturnType; /** * This is a small utility to get a type-safe instance of the Feedback integration. */ -export function getFeedback(): ReturnType | undefined { +export function getFeedback(): ReturnType | undefined { const client = getClient(); - return client && client.getIntegrationByName>('Feedback'); + return client && client.getIntegrationByName('Feedback'); } diff --git a/packages/feedback/src/core/integration.ts b/packages/feedback/src/core/integration.ts index b4a513ab3b9f..34f88886ece7 100644 --- a/packages/feedback/src/core/integration.ts +++ b/packages/feedback/src/core/integration.ts @@ -4,6 +4,7 @@ import type { FeedbackInternalOptions, FeedbackModalIntegration, FeedbackScreenshotIntegration, + Integration, IntegrationFn, } from '@sentry/types'; import { isBrowser, logger } from '@sentry/utils'; @@ -23,7 +24,6 @@ import { SUBMIT_BUTTON_LABEL, SUCCESS_MESSAGE_TEXT, } from '../constants'; -import { feedbackModalIntegration } from '../modal/integration'; import { DEBUG_BUILD } from '../util/debug-build'; import { isScreenshotSupported } from '../util/isScreenshotSupported'; import { mergeOptions } from '../util/mergeOptions'; @@ -37,238 +37,257 @@ type Unsubscribe = () => void; /** * Allow users to capture user feedback and send it to Sentry. */ -export const feedbackIntegration = (({ - // FeedbackGeneralConfiguration - id = 'sentry-feedback', - showBranding = true, - autoInject = true, - showEmail = true, - showName = true, - showScreenshot = false, - useSentryUser = { - email: 'email', - name: 'username', - }, - isNameRequired = false, - isEmailRequired = false, - // FeedbackThemeConfiguration - colorScheme = 'system', - themeLight, - themeDark, - - // FeedbackTextConfiguration - buttonLabel = ACTOR_LABEL, - cancelButtonLabel = CANCEL_BUTTON_LABEL, - submitButtonLabel = SUBMIT_BUTTON_LABEL, - formTitle = FORM_TITLE, - emailLabel = EMAIL_LABEL, - emailPlaceholder = EMAIL_PLACEHOLDER, - messageLabel = MESSAGE_LABEL, - messagePlaceholder = MESSAGE_PLACEHOLDER, - nameLabel = NAME_LABEL, - namePlaceholder = NAME_PLACEHOLDER, - successMessageText = SUCCESS_MESSAGE_TEXT, - isRequiredText = IS_REQUIRED_TEXT, - - // FeedbackCallbacks - onFormOpen, - onFormClose, - onSubmitSuccess, - onSubmitError, - onFormSubmitted, -}: OptionalFeedbackConfiguration = {}) => { - const _options = { - id, - autoInject, - showBranding, - isEmailRequired, - isNameRequired, - showEmail, - showName, - showScreenshot, - useSentryUser, - - colorScheme, - themeDark: { - ...DEFAULT_THEME.dark, - ...themeDark, - }, - themeLight: { - ...DEFAULT_THEME.light, - ...themeLight, +interface BuilderOptions { + // The type here should be `keyof typeof LazyLoadableIntegrations`, but that'll cause a cicrular + // dependency with @sentry/core + lazyLoadIntegration: (name: 'feedbackModalIntegration' | 'feedbackScreenshotIntegration') => Promise; + getModalIntegration?: null | (() => IntegrationFn); + getScreenshotIntegration?: null | (() => IntegrationFn); +} +export const buildFeedbackIntegration = ({ + lazyLoadIntegration, + getModalIntegration, + getScreenshotIntegration, +}: BuilderOptions): IntegrationFn => { + const feedbackIntegration = (({ + // FeedbackGeneralConfiguration + id = 'sentry-feedback', + showBranding = true, + autoInject = true, + showEmail = true, + showName = true, + showScreenshot = false, + useSentryUser = { + email: 'email', + name: 'username', }, + isNameRequired = false, + isEmailRequired = false, - buttonLabel, - cancelButtonLabel, - submitButtonLabel, - formTitle, - emailLabel, - emailPlaceholder, - messageLabel, - messagePlaceholder, - nameLabel, - namePlaceholder, - successMessageText, - isRequiredText, + // FeedbackThemeConfiguration + colorScheme = 'system', + themeLight, + themeDark, - onFormClose, + // FeedbackTextConfiguration + buttonLabel = ACTOR_LABEL, + cancelButtonLabel = CANCEL_BUTTON_LABEL, + submitButtonLabel = SUBMIT_BUTTON_LABEL, + formTitle = FORM_TITLE, + emailLabel = EMAIL_LABEL, + emailPlaceholder = EMAIL_PLACEHOLDER, + messageLabel = MESSAGE_LABEL, + messagePlaceholder = MESSAGE_PLACEHOLDER, + nameLabel = NAME_LABEL, + namePlaceholder = NAME_PLACEHOLDER, + successMessageText = SUCCESS_MESSAGE_TEXT, + isRequiredText = IS_REQUIRED_TEXT, + + // FeedbackCallbacks onFormOpen, - onSubmitError, + onFormClose, onSubmitSuccess, + onSubmitError, onFormSubmitted, - }; + }: OptionalFeedbackConfiguration = {}) => { + const _options = { + id, + autoInject, + showBranding, + isEmailRequired, + isNameRequired, + showEmail, + showName, + showScreenshot, + useSentryUser, - let _shadow: ShadowRoot | null = null; - let _subscriptions: Unsubscribe[] = []; + colorScheme, + themeDark: { + ...DEFAULT_THEME.dark, + ...themeDark, + }, + themeLight: { + ...DEFAULT_THEME.light, + ...themeLight, + }, - /** - * Get the shadow root where we will append css - */ - const _createShadow = (options: FeedbackInternalOptions): ShadowRoot => { - if (!_shadow) { - const host = DOCUMENT.createElement('div'); - host.id = String(options.id); - DOCUMENT.body.appendChild(host); + buttonLabel, + cancelButtonLabel, + submitButtonLabel, + formTitle, + emailLabel, + emailPlaceholder, + messageLabel, + messagePlaceholder, + nameLabel, + namePlaceholder, + successMessageText, + isRequiredText, - _shadow = host.attachShadow({ mode: 'open' }); - _shadow.appendChild(createMainStyles(options.colorScheme, options)); - } - return _shadow as ShadowRoot; - }; + onFormClose, + onFormOpen, + onSubmitError, + onSubmitSuccess, + onFormSubmitted, + }; - const _loadAndRenderDialog = async (options: FeedbackInternalOptions): Promise => { - const client = getClient(); // TODO: getClient() - if (!client) { - throw new Error('Sentry Client is not initialized correctly'); - } - const modalIntegration: FeedbackModalIntegration = feedbackModalIntegration(); - client.addIntegration(modalIntegration); - const screenshotIntegration = client.getIntegrationByName('FeedbackScreenshot'); - const screenshotIsSupported = isScreenshotSupported(); + let _shadow: ShadowRoot | null = null; + let _subscriptions: Unsubscribe[] = []; - // START TEMP: Error messages - if (!modalIntegration && showScreenshot && !screenshotIntegration) { - throw new Error('Async loading of Feedback Modal & Screenshot integrations is not yet implemented'); - } else if (!modalIntegration) { - throw new Error('Async loading of Feedback Modal is not yet implemented'); - } else if (showScreenshot && !screenshotIntegration) { - throw new Error('Async loading of Feedback Screenshot integration is not yet implemented'); - } - // END TEMP + /** + * Get the shadow root where we will append css + */ + const _createShadow = (options: FeedbackInternalOptions): ShadowRoot => { + if (!_shadow) { + const host = DOCUMENT.createElement('div'); + host.id = String(options.id); + DOCUMENT.body.appendChild(host); - if (!modalIntegration) { - // TODO: load modalIntegration - throw new Error('Not implemented yet'); - } + _shadow = host.attachShadow({ mode: 'open' }); + _shadow.appendChild(createMainStyles(options.colorScheme, options)); + } + return _shadow as ShadowRoot; + }; - if (showScreenshot && !screenshotIntegration && screenshotIsSupported) { - // TODO: load screenshotIntegration - throw new Error('Not implemented yet'); - } + const _findIntegration = async ( + integrationName: string, + getter: undefined | null | (() => IntegrationFn), + functionMethodName: 'feedbackModalIntegration' | 'feedbackScreenshotIntegration', + ): Promise => { + const client = getClient(); + const existing = client && client.getIntegrationByName(integrationName); + if (existing) { + return existing as I; + } + const integrationFn = (getter && getter()) || (await lazyLoadIntegration(functionMethodName)); + const integration = integrationFn(); + client && client.addIntegration(integration); + return integration as I; + }; - return modalIntegration.createDialog({ - options, - screenshotIntegration: screenshotIsSupported ? screenshotIntegration : undefined, - sendFeedback, - shadow: _createShadow(options), - }); - }; + const _loadAndRenderDialog = async (options: FeedbackInternalOptions): Promise => { + const [modalIntegration, screenshotIntegration] = await Promise.all([ + _findIntegration('FeedbackModal', getModalIntegration, 'feedbackModalIntegration'), + showScreenshot && isScreenshotSupported() + ? _findIntegration( + 'FeedbackScreenshot', + getScreenshotIntegration, + 'feedbackScreenshotIntegration', + ) + : undefined, + ]); + if (!modalIntegration || (showScreenshot && !screenshotIntegration)) { + // TODO: Let the end-user retry async loading + // Include more verbose logs so developers can understand the options (like preloading). + throw new Error('Missing feedback helper integration!'); + } - const attachTo = (el: Element | string, optionOverrides: OverrideFeedbackConfiguration = {}): Unsubscribe => { - const mergedOptions = mergeOptions(_options, optionOverrides); + return modalIntegration.createDialog({ + options, + screenshotIntegration: showScreenshot ? screenshotIntegration : undefined, + sendFeedback, + shadow: _createShadow(options), + }); + }; - const targetEl = - typeof el === 'string' ? DOCUMENT.querySelector(el) : typeof el.addEventListener === 'function' ? el : null; + const attachTo = (el: Element | string, optionOverrides: OverrideFeedbackConfiguration = {}): Unsubscribe => { + const mergedOptions = mergeOptions(_options, optionOverrides); - if (!targetEl) { - DEBUG_BUILD && logger.error('[Feedback] Unable to attach to target element'); - throw new Error('Unable to attach to target element'); - } + const targetEl = + typeof el === 'string' ? DOCUMENT.querySelector(el) : typeof el.addEventListener === 'function' ? el : null; - let dialog: FeedbackDialog | null = null; - const handleClick = async (): Promise => { - if (!dialog) { - dialog = await _loadAndRenderDialog({ - ...mergedOptions, - onFormClose: () => { - dialog && dialog.close(); - mergedOptions.onFormClose && mergedOptions.onFormClose(); - }, - onFormSubmitted: () => { - dialog && dialog.removeFromDom(); - mergedOptions.onFormSubmitted && mergedOptions.onFormSubmitted(); - }, - }); + if (!targetEl) { + DEBUG_BUILD && logger.error('[Feedback] Unable to attach to target element'); + throw new Error('Unable to attach to target element'); } - dialog.appendToDom(); - dialog.open(); - }; - targetEl.addEventListener('click', handleClick); - const unsubscribe = (): void => { - _subscriptions = _subscriptions.filter(sub => sub !== unsubscribe); - dialog && dialog.removeFromDom(); - dialog = null; - targetEl.removeEventListener('click', handleClick); + + let dialog: FeedbackDialog | null = null; + const handleClick = async (): Promise => { + if (!dialog) { + dialog = await _loadAndRenderDialog({ + ...mergedOptions, + onFormClose: () => { + dialog && dialog.close(); + mergedOptions.onFormClose && mergedOptions.onFormClose(); + }, + onFormSubmitted: () => { + dialog && dialog.removeFromDom(); + mergedOptions.onFormSubmitted && mergedOptions.onFormSubmitted(); + }, + }); + } + dialog.appendToDom(); + dialog.open(); + }; + targetEl.addEventListener('click', handleClick); + const unsubscribe = (): void => { + _subscriptions = _subscriptions.filter(sub => sub !== unsubscribe); + dialog && dialog.removeFromDom(); + dialog = null; + targetEl.removeEventListener('click', handleClick); + }; + _subscriptions.push(unsubscribe); + return unsubscribe; }; - _subscriptions.push(unsubscribe); - return unsubscribe; - }; - const autoInjectActor = (): void => { - const shadow = _createShadow(_options); - const actor = Actor({ buttonLabel: _options.buttonLabel, shadow }); - const mergedOptions = mergeOptions(_options, { - onFormOpen() { - actor.removeFromDom(); - }, - onFormClose() { - actor.appendToDom(); - }, - onFormSubmitted() { - actor.appendToDom(); - }, - }); - attachTo(actor.el, mergedOptions); + const autoInjectActor = (): void => { + const shadow = _createShadow(_options); + const actor = Actor({ buttonLabel: _options.buttonLabel, shadow }); + const mergedOptions = mergeOptions(_options, { + onFormOpen() { + actor.removeFromDom(); + }, + onFormClose() { + actor.appendToDom(); + }, + onFormSubmitted() { + actor.appendToDom(); + }, + }); + attachTo(actor.el, mergedOptions); - actor.appendToDom(); - }; + actor.appendToDom(); + }; - return { - name: 'Feedback', - setupOnce() { - if (!isBrowser() || !_options.autoInject) { - return; - } + return { + name: 'Feedback', + setupOnce() { + if (!isBrowser() || !_options.autoInject) { + return; + } - autoInjectActor(); - }, + autoInjectActor(); + }, - /** - * Adds click listener to the element to open a feedback dialog - * - * The returned function can be used to remove the click listener - */ - attachTo, + /** + * Adds click listener to the element to open a feedback dialog + * + * The returned function can be used to remove the click listener + */ + attachTo, - /** - * Creates a new widget. Accepts partial options to override any options passed to constructor. - */ - async createWidget(optionOverrides: OverrideFeedbackConfiguration = {}): Promise { - return _loadAndRenderDialog(mergeOptions(_options, optionOverrides)); - }, + /** + * Creates a new widget. Accepts partial options to override any options passed to constructor. + */ + async createWidget(optionOverrides: OverrideFeedbackConfiguration = {}): Promise { + return _loadAndRenderDialog(mergeOptions(_options, optionOverrides)); + }, - /** - * Removes the Feedback integration (including host, shadow DOM, and all widgets) - */ - remove(): void { - if (_shadow) { - _shadow.parentElement && _shadow.parentElement.remove(); - _shadow = null; - } - // Remove any lingering subscriptions - _subscriptions.forEach(sub => sub()); - _subscriptions = []; - }, - }; -}) satisfies IntegrationFn; + /** + * Removes the Feedback integration (including host, shadow DOM, and all widgets) + */ + remove(): void { + if (_shadow) { + _shadow.parentElement && _shadow.parentElement.remove(); + _shadow = null; + } + // Remove any lingering subscriptions + _subscriptions.forEach(sub => sub()); + _subscriptions = []; + }, + }; + }) satisfies IntegrationFn; + + return feedbackIntegration; +}; diff --git a/packages/feedback/src/index.bundle.ts b/packages/feedback/src/index.bundle.ts deleted file mode 100644 index a384487e935b..000000000000 --- a/packages/feedback/src/index.bundle.ts +++ /dev/null @@ -1,6 +0,0 @@ -// This file is used as entry point to generate the integration CDN bundle for the core feedback integration -// For now this includes the modal as well, but not feedback -export { sendFeedback } from './core/sendFeedback'; -export { feedbackIntegration } from './core/integration'; -export { feedbackModalIntegration } from './modal/integration'; -export { getFeedback } from './core/getFeedback'; diff --git a/packages/feedback/src/index.ts b/packages/feedback/src/index.ts index 245469a7f3d6..2f95cf86fd1f 100644 --- a/packages/feedback/src/index.ts +++ b/packages/feedback/src/index.ts @@ -1,5 +1,7 @@ +// This file is used as entry point to generate the npm package and CDN bundles. + export { sendFeedback } from './core/sendFeedback'; -export { feedbackIntegration } from './core/integration'; -export { feedbackModalIntegration } from './modal/integration'; +export { buildFeedbackIntegration } from './core/integration'; export { getFeedback } from './core/getFeedback'; +export { feedbackModalIntegration } from './modal/integration'; export { feedbackScreenshotIntegration } from './screenshot/integration'; diff --git a/packages/integration-shims/src/Feedback.ts b/packages/integration-shims/src/Feedback.ts index b0bb3f564779..189dc074cd1e 100644 --- a/packages/integration-shims/src/Feedback.ts +++ b/packages/integration-shims/src/Feedback.ts @@ -2,23 +2,11 @@ import type { Integration } from '@sentry/types'; import { consoleSandbox } from '@sentry/utils'; import { FAKE_FUNCTION } from './common'; -const FEEDBACK_INTEGRATION_METHODS = [ - 'openDialog', - 'closeDialog', - 'attachTo', - 'createWidget', - 'removeWidget', - 'getWidget', - 'remove', -] as const; +const FEEDBACK_INTEGRATION_METHODS = ['attachTo', 'createWidget', 'remove'] as const; type FeedbackSpecificMethods = Record<(typeof FEEDBACK_INTEGRATION_METHODS)[number], () => void>; -type FeedbackModalSpecificMethods = { createDialog: () => void }; -type FeedbackScreenshotSpecificMethods = { createInput: () => void }; interface FeedbackIntegration extends Integration, FeedbackSpecificMethods {} -interface FeedbackModalIntegration extends Integration, FeedbackModalSpecificMethods {} -interface FeedbackScreenshotIntegration extends Integration, FeedbackScreenshotSpecificMethods {} /** * This is a shim for the Feedback integration. @@ -39,27 +27,3 @@ export function feedbackIntegrationShim(_options: unknown): FeedbackIntegration }, {} as FeedbackSpecificMethods) as FeedbackSpecificMethods), }; } - -/** - * This is a shim for the FeedbackModal integration. - * It is needed in order for the CDN bundles to continue working when users add/remove feedback - * from it, without changing their config. This is necessary for the loader mechanism. - */ -export function feedbackModalIntegrationShim(): FeedbackModalIntegration { - return { - name: 'FeedbackModal', - createDialog: FAKE_FUNCTION, - }; -} - -/** - * This is a shim for the FeedbackScreenshot integration. - * It is needed in order for the CDN bundles to continue working when users add/remove feedback - * from it, without changing their config. This is necessary for the loader mechanism. - */ -export function feedbackScreenshotIntegrationShim(): FeedbackScreenshotIntegration { - return { - name: 'FeedbackScreenshot', - createInput: FAKE_FUNCTION, - }; -} diff --git a/packages/integration-shims/src/index.ts b/packages/integration-shims/src/index.ts index 2d1186017d4e..510b26ddbb76 100644 --- a/packages/integration-shims/src/index.ts +++ b/packages/integration-shims/src/index.ts @@ -1,7 +1,3 @@ -export { - feedbackIntegrationShim, - feedbackModalIntegrationShim, - feedbackScreenshotIntegrationShim, -} from './Feedback'; +export { feedbackIntegrationShim } from './Feedback'; export { replayIntegrationShim } from './Replay'; export { browserTracingIntegrationShim } from './BrowserTracing';