capacitor.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. import {buildSdkConfig} from 'sentry/components/onboarding/gettingStartedDoc/buildSdkConfig';
  2. import crashReportCallout from 'sentry/components/onboarding/gettingStartedDoc/feedback/crashReportCallout';
  3. import widgetCallout from 'sentry/components/onboarding/gettingStartedDoc/feedback/widgetCallout';
  4. import TracePropagationMessage from 'sentry/components/onboarding/gettingStartedDoc/replay/tracePropagationMessage';
  5. import {StepType} from 'sentry/components/onboarding/gettingStartedDoc/step';
  6. import type {
  7. Docs,
  8. DocsParams,
  9. OnboardingConfig,
  10. PlatformOption,
  11. } from 'sentry/components/onboarding/gettingStartedDoc/types';
  12. import {getUploadSourceMapsStep} from 'sentry/components/onboarding/gettingStartedDoc/utils';
  13. import {
  14. getCrashReportJavaScriptInstallStep,
  15. getCrashReportModalConfigDescription,
  16. getCrashReportModalIntroduction,
  17. getFeedbackConfigOptions,
  18. getFeedbackConfigureDescription,
  19. } from 'sentry/components/onboarding/gettingStartedDoc/utils/feedbackOnboarding';
  20. import {
  21. getReplayConfigOptions,
  22. getReplayConfigureDescription,
  23. getReplayVerifyStep,
  24. } from 'sentry/components/onboarding/gettingStartedDoc/utils/replayOnboarding';
  25. import {t, tct} from 'sentry/locale';
  26. export enum SiblingOption {
  27. ANGULARV10 = 'angularV10',
  28. ANGULARV12 = 'angularV12',
  29. REACT = 'react',
  30. VUE3 = 'vue3',
  31. VUE2 = 'vue2',
  32. }
  33. type PlatformOptionKey = 'siblingOption';
  34. const platformOptions: Record<PlatformOptionKey, PlatformOption> = {
  35. siblingOption: {
  36. label: t('Sibling Package'),
  37. items: [
  38. {
  39. label: t('Angular 12+'),
  40. value: SiblingOption.ANGULARV12,
  41. },
  42. {
  43. label: t('Angular 10 & 11'),
  44. value: SiblingOption.ANGULARV10,
  45. },
  46. {
  47. label: t('React'),
  48. value: SiblingOption.REACT,
  49. },
  50. {
  51. label: t('Vue 3'),
  52. value: SiblingOption.VUE3,
  53. },
  54. {
  55. label: t('Vue 2'),
  56. value: SiblingOption.VUE2,
  57. },
  58. ],
  59. },
  60. };
  61. type PlatformOptions = typeof platformOptions;
  62. type Params = DocsParams<PlatformOptions>;
  63. const isAngular = (siblingOption: string): boolean =>
  64. siblingOption === SiblingOption.ANGULARV10 ||
  65. siblingOption === SiblingOption.ANGULARV12;
  66. const isVue = (siblingOption: string): boolean =>
  67. siblingOption === SiblingOption.VUE2 || siblingOption === SiblingOption.VUE3;
  68. function getPerformanceIntegration(siblingOption: string): string {
  69. return `${
  70. isVue(siblingOption)
  71. ? `routingInstrumentation: SentryVue.vueRouterInstrumentation(router),`
  72. : isAngular(siblingOption)
  73. ? `routingInstrumentation: SentryAngular.routingInstrumentation,`
  74. : ''
  75. }`;
  76. }
  77. const performanceAngularErrorHandler = `,
  78. {
  79. provide: SentryAngular.TraceService,
  80. deps: [Router],
  81. },
  82. {
  83. provide: APP_INITIALIZER,
  84. useFactory: () => () => {},
  85. deps: [SentryAngular.TraceService],
  86. multi: true,
  87. },`;
  88. const onboarding: OnboardingConfig<PlatformOptions> = {
  89. install: (params: Params) => [
  90. {
  91. type: StepType.INSTALL,
  92. description: (
  93. <p>
  94. {tct(
  95. `Install the Sentry Capacitor SDK as a dependency using [code:npm] or [code:yarn], alongside the Sentry [siblingName:] SDK:`,
  96. {
  97. code: <code />,
  98. siblingName: getSiblingName(params.platformOptions.siblingOption),
  99. }
  100. )}
  101. </p>
  102. ),
  103. configurations: [
  104. {
  105. language: 'bash',
  106. code: [
  107. {
  108. label: 'npm',
  109. value: 'npm',
  110. language: 'bash',
  111. code: `npm install --save @sentry/capacitor ${getNpmPackage(
  112. params.platformOptions.siblingOption
  113. )}@^7`,
  114. },
  115. {
  116. label: 'yarn',
  117. value: 'yarn',
  118. language: 'bash',
  119. code: `yarn add @sentry/capacitor ${getNpmPackage(
  120. params.platformOptions.siblingOption
  121. )}@^7 --exact`,
  122. },
  123. ],
  124. },
  125. {
  126. additionalInfo: (
  127. <p>
  128. {tct(
  129. `The version of the Sentry [siblingName:] SDK must match with the version referred by Sentry Capacitor. To check which version of the Sentry [siblingName:] SDK is installed, use the following command: [code:npm info @sentry/capacitor peerDependencies]`,
  130. {
  131. code: <code />,
  132. siblingName: getSiblingName(params.platformOptions.siblingOption),
  133. }
  134. )}
  135. </p>
  136. ),
  137. },
  138. ],
  139. },
  140. ],
  141. configure: params => [
  142. {
  143. type: StepType.CONFIGURE,
  144. configurations: getSetupConfiguration({params, showExtraStep: true}),
  145. },
  146. getUploadSourceMapsStep({
  147. guideLink:
  148. 'https://docs.sentry.io/platforms/javascript/guides/capacitor/sourcemaps/',
  149. ...params,
  150. }),
  151. ],
  152. verify: _ => [
  153. {
  154. type: StepType.VERIFY,
  155. description: t(
  156. "This snippet contains an intentional error and can be used as a test to make sure that everything's working as expected."
  157. ),
  158. configurations: [
  159. {
  160. language: 'javascript',
  161. code: `myUndefinedFunction();`,
  162. },
  163. ],
  164. },
  165. ],
  166. nextSteps: () => [
  167. {
  168. id: 'capacitor-android-setup',
  169. name: t('Capacitor 2 Setup'),
  170. description: t(
  171. 'If you are using Capacitor 2 or older, follow this step to add required changes in order to initialize the Capacitor SDK on Android.'
  172. ),
  173. link: 'https://docs.sentry.io/platforms/javascript/guides/capacitor/?#capacitor-2---android-specifics',
  174. },
  175. ],
  176. };
  177. function getSiblingImportsSetupConfiguration(siblingOption: string): string {
  178. switch (siblingOption) {
  179. case SiblingOption.VUE3:
  180. return `import {createApp} from "vue";
  181. import {createRouter} from "vue-router";`;
  182. case SiblingOption.VUE2:
  183. return `import Vue from "vue";
  184. import Router from "vue-router";`;
  185. default:
  186. return '';
  187. }
  188. }
  189. function getVueConstSetup(siblingOption: string): string {
  190. switch (siblingOption) {
  191. case SiblingOption.VUE3:
  192. return `
  193. const app = createApp({
  194. // ...
  195. });
  196. const router = createRouter({
  197. // ...
  198. });
  199. `;
  200. case SiblingOption.VUE2:
  201. return `
  202. Vue.use(Router);
  203. const router = new Router({
  204. // ...
  205. });
  206. `;
  207. default:
  208. return '';
  209. }
  210. }
  211. const getIntegrations = (params: Params): string[] => {
  212. const integrations: string[] = ['SentrySibling.browserTracingIntegration()'];
  213. if (params.isPerformanceSelected) {
  214. integrations.push(`
  215. new ${getSiblingImportName(params.platformOptions.siblingOption)}.BrowserTracing({
  216. // Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled
  217. tracePropagationTargets: ["localhost", /^https:\\/\\/yourserver\\.io\\/api/],
  218. ${getPerformanceIntegration(params.platformOptions.siblingOption)}
  219. })`);
  220. }
  221. if (params.isReplaySelected) {
  222. integrations.push(
  223. `new ${getSiblingImportName(params.platformOptions.siblingOption)}.Replay(${getReplayConfigOptions(
  224. params.replayOptions
  225. )})`
  226. );
  227. }
  228. if (params.isFeedbackSelected) {
  229. integrations.push(`
  230. Sentry.feedbackIntegration({
  231. colorScheme: "system",
  232. ${getFeedbackConfigOptions(params.feedbackOptions)}
  233. })`);
  234. }
  235. return integrations;
  236. };
  237. const getDynamicParts = (params: Params): string[] => {
  238. const dynamicParts: string[] = [];
  239. if (params.isPerformanceSelected) {
  240. dynamicParts.push(`
  241. // Tracing
  242. tracesSampleRate: 1.0 // Capture 100% of the transactions`);
  243. }
  244. if (params.isReplaySelected) {
  245. dynamicParts.push(`
  246. // Session Replay
  247. replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
  248. replaysOnErrorSampleRate: 1.0 // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.`);
  249. }
  250. return dynamicParts;
  251. };
  252. const getStaticParts = (params: Params): string[] => {
  253. const staticParts = [`dsn: "${params.dsn.public}"`];
  254. if (params.platformOptions.siblingOption === SiblingOption.VUE2) {
  255. staticParts.unshift(`Vue`);
  256. } else if (params.platformOptions.siblingOption === SiblingOption.VUE3) {
  257. staticParts.unshift(`app`);
  258. }
  259. return staticParts;
  260. };
  261. function getSetupConfiguration({
  262. params,
  263. showExtraStep,
  264. showDescription,
  265. }: {
  266. params: Params;
  267. showExtraStep: boolean;
  268. showDescription?: boolean;
  269. }) {
  270. const siblingOption = params.platformOptions.siblingOption;
  271. const config = buildSdkConfig({
  272. params,
  273. staticParts: getStaticParts(params),
  274. getIntegrations,
  275. getDynamicParts,
  276. });
  277. const configuration = [
  278. {
  279. description: showDescription
  280. ? tct(
  281. `You should init the Sentry capacitor SDK in your [code:main.ts] file as soon as possible during application load up, before initializing Sentry [siblingName:]:`,
  282. {
  283. siblingName: getSiblingName(siblingOption),
  284. code: <code />,
  285. }
  286. )
  287. : null,
  288. language: 'javascript',
  289. code: `${getSiblingImportsSetupConfiguration(siblingOption)}
  290. import * as Sentry from '@sentry/capacitor';
  291. import * as ${getSiblingImportName(siblingOption)} from '${getNpmPackage(
  292. siblingOption
  293. )}';
  294. ${getVueConstSetup(siblingOption)}
  295. Sentry.init({
  296. ${config}
  297. },
  298. // Forward the init method from ${getNpmPackage(params.platformOptions.siblingOption)}
  299. ${getSiblingImportName(siblingOption)}.init
  300. );`,
  301. },
  302. ];
  303. if (isAngular(siblingOption) && showExtraStep) {
  304. configuration.push({
  305. description: tct(
  306. "The Sentry Angular SDK exports a function to instantiate ErrorHandler provider that will automatically send JavaScript errors captured by the Angular's error handler.",
  307. {}
  308. ),
  309. language: 'javascript',
  310. code: `
  311. import { APP_INITIALIZER, ErrorHandler, NgModule } from "@angular/core";
  312. import { Router } from "@angular/router";
  313. import * as SentryAngular from "${getNpmPackage(siblingOption)}";
  314. @NgModule({
  315. // ...
  316. providers: [
  317. {
  318. provide: ErrorHandler,
  319. useValue: SentryAngular.createErrorHandler(),
  320. }${params.isPerformanceSelected ? performanceAngularErrorHandler : ' '}
  321. ],
  322. // ...
  323. })
  324. export class AppModule {}`,
  325. });
  326. }
  327. return configuration;
  328. }
  329. function getNpmPackage(siblingOption: string): string {
  330. const packages: Record<SiblingOption, string> = {
  331. [SiblingOption.ANGULARV10]: '@sentry/angular',
  332. [SiblingOption.ANGULARV12]: '@sentry/angular-ivy',
  333. [SiblingOption.REACT]: '@sentry/react',
  334. [SiblingOption.VUE3]: '@sentry/vue',
  335. [SiblingOption.VUE2]: '@sentry/vue',
  336. };
  337. // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
  338. return packages[siblingOption];
  339. }
  340. function getSiblingName(siblingOption: string): string {
  341. switch (siblingOption) {
  342. case SiblingOption.ANGULARV10:
  343. case SiblingOption.ANGULARV12:
  344. return 'Angular';
  345. case SiblingOption.REACT:
  346. return 'React';
  347. case SiblingOption.VUE2:
  348. case SiblingOption.VUE3:
  349. return 'Vue';
  350. default:
  351. return '';
  352. }
  353. }
  354. function getSiblingImportName(siblingOption: string): string {
  355. switch (siblingOption) {
  356. case SiblingOption.ANGULARV10:
  357. case SiblingOption.ANGULARV12:
  358. return 'SentryAngular';
  359. case SiblingOption.REACT:
  360. return 'SentryReact';
  361. case SiblingOption.VUE2:
  362. case SiblingOption.VUE3:
  363. return 'SentryVue';
  364. default:
  365. return '';
  366. }
  367. }
  368. const replayOnboarding: OnboardingConfig<PlatformOptions> = {
  369. install: params => onboarding.install(params),
  370. configure: params => [
  371. {
  372. type: StepType.CONFIGURE,
  373. description: getReplayConfigureDescription({
  374. link: 'https://docs.sentry.io/platforms/javascript/guides/capacitor/session-replay/',
  375. }),
  376. configurations: getSetupConfiguration({
  377. params,
  378. showExtraStep: false,
  379. showDescription: false,
  380. }),
  381. additionalInfo: <TracePropagationMessage />,
  382. },
  383. ],
  384. verify: getReplayVerifyStep(),
  385. nextSteps: () => [],
  386. };
  387. const feedbackOnboarding: OnboardingConfig<PlatformOptions> = {
  388. install: (params: Params) => onboarding.install(params),
  389. configure: (params: Params) => [
  390. {
  391. type: StepType.CONFIGURE,
  392. description: getFeedbackConfigureDescription({
  393. linkConfig:
  394. 'https://docs.sentry.io/platforms/javascript/guides/capacitor/user-feedback/configuration/',
  395. linkButton:
  396. 'https://docs.sentry.io/platforms/javascript/guides/capacitor/user-feedback/configuration/#bring-your-own-button',
  397. }),
  398. configurations: getSetupConfiguration({
  399. params,
  400. showExtraStep: false,
  401. showDescription: false,
  402. }),
  403. additionalInfo: crashReportCallout({
  404. link: 'https://docs.sentry.io/platforms/javascript/guides/capacitor/user-feedback/#crash-report-modal',
  405. }),
  406. },
  407. ],
  408. verify: () => [],
  409. nextSteps: () => [],
  410. };
  411. const crashReportOnboarding: OnboardingConfig<PlatformOptions> = {
  412. introduction: () => getCrashReportModalIntroduction(),
  413. install: (params: Params) => getCrashReportJavaScriptInstallStep(params),
  414. configure: () => [
  415. {
  416. type: StepType.CONFIGURE,
  417. description: getCrashReportModalConfigDescription({
  418. link: 'https://docs.sentry.io/platforms/javascript/guides/capacitor/user-feedback/configuration/#crash-report-modal',
  419. }),
  420. additionalInfo: widgetCallout({
  421. link: 'https://docs.sentry.io/platforms/javascript/guides/capacitor/user-feedback/#user-feedback-widget',
  422. }),
  423. },
  424. ],
  425. verify: () => [],
  426. nextSteps: () => [],
  427. };
  428. const docs: Docs<PlatformOptions> = {
  429. onboarding,
  430. platformOptions,
  431. feedbackOnboardingNpm: feedbackOnboarding,
  432. replayOnboarding,
  433. crashReportOnboarding,
  434. };
  435. export default docs;