fix(vue): Update Vue trackComponents list to match components with or without <> - #13543

Merged
Lms24 merged 5 commits into
getsentry:developfrom
Zen-cronic:fix/trackComponents-allowList
Sep 9, 2024
Merged

fix(vue): Update Vue trackComponents list to match components with or without <>#13543
Lms24 merged 5 commits into
getsentry:developfrom
Zen-cronic:fix/trackComponents-allowList

Conversation

@Zen-cronic

Copy link
Copy Markdown
Contributor

Resolves#13510

Tests to be added.

  • If you've added code that should be tested, please add tests.
  • Ensure your code lints and the test suite passes (yarn lint) & (yarn test).

…or without `<>`
Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
Comment threadpackages/vue/src/tracing.ts Outdated
if (!(currentCompo.startsWith('<') && currentCompo.endsWith('>'))) {
currentCompo = `<${currentCompo}>`;
}
return formattedName === currentCompo;

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formattedName from formatComponentName() always return a name with < >.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, it can also be <Component> at Component.vue.

@Zen-cronic
Zen-cronic marked this pull request as ready for review September 3, 2024 01:00

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @Zen-cronic thanks for opening this PR!

The change looks good to me but I had a slightly more size-efficient solution.

Could you add a test as well? We already have a Vue 3 test E2E test application. I was thinking that we could set a trackComponents array option in the Sentry init call and adjust the pageload test that it also includes a ui.vue.* span for a component that matches one of the array entries.

If you don't have time, just let me know, then I'll add the test.

Comment threadpackages/vue/src/tracing.ts Outdated
Comment on lines +52 to +57
let currentCompo = compo;

if (!(currentCompo.startsWith('<') && currentCompo.endsWith('>'))) {
currentCompo = `<${currentCompo}>`;
}
return formattedName === currentCompo;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can replace this with a more bundle size efficient version:

Suggested change
letcurrentCompo=compo;
if(!(currentCompo.startsWith('<')&&currentCompo.endsWith('>'))){
currentCompo=`<${currentCompo}>`;
}
returnformattedName===currentCompo;
returncompo.replace(/^<(.*)>$/,'$1')===formattedName

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah in this case we don't even need the block anymore but could inline it to the some call. So feel free to do that as well :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, I've taken your approach and found that formattedName must also be replaced:

functionfindTrackComponent(trackComponents: string[],formattedName: string): boolean{functionextractComponentName(name: string): string{returnname.replace(/^<(.*)>(?:.*)?$/,"$1");}constisMatched=trackComponents.some(compo=>{returnextractComponentName(formattedName)===extractComponentName(compo)});returnisMatched;}

The regex is needed for both the user provided component and the formatted component.

  • User provided: either <App> or App
  • Formatted: either <App> or <App> at App.vue.

What do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation, you're right! Also, I didn't think about the <App> at App.vue case.

There's one unfortunate side effect of this regex though: It can lead to polynomial build up when evaluating an input. Given that we run extractComponentName on user input I'd rather not risk the (still unlikely but possible) chance of some kind of ReDoS attack (I used this ReDoS checker to test it)

I played around with the regex to make it safer and keep it linear and arrived at this one: /^<([^\s]*)>( at [^\s]*)?$
I think this should still cover all component names but would appreciate it if you could double check it as well :)

Also, since this logic is getting a little bit more involved now, could you add a couple of unit test for findTrackComponent? It's fine to export the function just for testing purposes btw. Thanks!

(I don't want to overburden you with tests btw, I just want to keep stuff like this covered and understandable for everyone. So if you don't have the time, just let me know. We appreciate your hard work either way!)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted, unit and e2e tests added.

@Lms24Lms24 self-assigned this Sep 4, 2024
@Zen-cronic

Copy link
Copy Markdown
ContributorAuthor

Just read your comments and review, I'll take them up now. Thanks!

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I realized I approved the PR yesterday on accident. Please don't mind the change request, I'm just putting it here so that we don't merge the PR accidentally before the extract name logic is resolved.

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
*
*/
function extractComponentName(name: string): string {
return name.replace(/^<([^\s]*)>(?: at [^\s]*)?$/, '$1');

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on what you suggested @Lms24, just an added non-capture group. The ReDoS Checker passes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me, thanks!

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
'sentry.op': 'ui.vue.mount',
'sentry.origin': 'auto.ui.vue',
},
description: 'Vue <<HomeView>>',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just making sure, are the double brackets intended? They're formatted here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation! I don't think they're intended. Let's remove them.

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the changes! We're almost there :)

});
});

test('sends a lifecycle span for the tracked HomeView component - with `<>`', async ({ page }) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of an unfortunate and very intransparent limitation of our e2e test setup but we shouldn't navigate to the same route in more than one e2e test. The reason is that we execute them in parallel in CI and the waitForTransaction call from one test might actually resolve for a transaction in another test, depending on the condition in the callback.

To avoid this confusion, we generally create a new route for each test. Suggestion: Either we create new routes for the two new tests or we integrate them in already existing tests and simply check for the spans there. I'll let you make the call, I'm fine with either option.
Also, feel free to test both kinds trackComponent notations in one route/test :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, thanks for the info. i decided to go with creating a new route, and grouped the different notations together in a single test.

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
Group trackComponent tests together:
1. With <>
2. Without <>
3. Not tracked
Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
<h1>Demonstrating Component Tracking</h1>
<ComponentOneView />
<ComponentTwoView />
</template>

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inspired by the sveltekit-2-svelte-5 test setup.

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot @Zen-cronic, looks great to me now!

By the way, the entire JS SDK team really appreciates your contributions! @mydea sent you a message with a small thank you gift on LinkedIn a couple of days ago, in case you missed it. If this message somehow got lost, please feel free to drop me or @mydea an email (email in GH bio) :)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vue trackComponents allowlist should match for components without <>

2 participants

@Zen-cronic@Lms24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(vue): Update Vue trackComponents list to match components with or without <> - #13543

Merged
Lms24 merged 5 commits into
getsentry:developfrom
Zen-cronic:fix/trackComponents-allowList
Sep 9, 2024
Merged

fix(vue): Update Vue trackComponents list to match components with or without <>#13543
Lms24 merged 5 commits into
getsentry:developfrom
Zen-cronic:fix/trackComponents-allowList

Conversation

@Zen-cronic

Copy link
Copy Markdown
Contributor

Resolves#13510

Tests to be added.

  • If you've added code that should be tested, please add tests.
  • Ensure your code lints and the test suite passes (yarn lint) & (yarn test).

…or without `<>`
Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
Comment threadpackages/vue/src/tracing.ts Outdated
if (!(currentCompo.startsWith('<') && currentCompo.endsWith('>'))) {
currentCompo = `<${currentCompo}>`;
}
return formattedName === currentCompo;

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formattedName from formatComponentName() always return a name with < >.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, it can also be <Component> at Component.vue.

@Zen-cronic
Zen-cronic marked this pull request as ready for review September 3, 2024 01:00

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @Zen-cronic thanks for opening this PR!

The change looks good to me but I had a slightly more size-efficient solution.

Could you add a test as well? We already have a Vue 3 test E2E test application. I was thinking that we could set a trackComponents array option in the Sentry init call and adjust the pageload test that it also includes a ui.vue.* span for a component that matches one of the array entries.

If you don't have time, just let me know, then I'll add the test.

Comment threadpackages/vue/src/tracing.ts Outdated
Comment on lines +52 to +57
let currentCompo = compo;

if (!(currentCompo.startsWith('<') && currentCompo.endsWith('>'))) {
currentCompo = `<${currentCompo}>`;
}
return formattedName === currentCompo;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can replace this with a more bundle size efficient version:

Suggested change
letcurrentCompo=compo;
if(!(currentCompo.startsWith('<')&&currentCompo.endsWith('>'))){
currentCompo=`<${currentCompo}>`;
}
returnformattedName===currentCompo;
returncompo.replace(/^<(.*)>$/,'$1')===formattedName

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah in this case we don't even need the block anymore but could inline it to the some call. So feel free to do that as well :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, I've taken your approach and found that formattedName must also be replaced:

functionfindTrackComponent(trackComponents: string[],formattedName: string): boolean{functionextractComponentName(name: string): string{returnname.replace(/^<(.*)>(?:.*)?$/,"$1");}constisMatched=trackComponents.some(compo=>{returnextractComponentName(formattedName)===extractComponentName(compo)});returnisMatched;}

The regex is needed for both the user provided component and the formatted component.

  • User provided: either <App> or App
  • Formatted: either <App> or <App> at App.vue.

What do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation, you're right! Also, I didn't think about the <App> at App.vue case.

There's one unfortunate side effect of this regex though: It can lead to polynomial build up when evaluating an input. Given that we run extractComponentName on user input I'd rather not risk the (still unlikely but possible) chance of some kind of ReDoS attack (I used this ReDoS checker to test it)

I played around with the regex to make it safer and keep it linear and arrived at this one: /^<([^\s]*)>( at [^\s]*)?$
I think this should still cover all component names but would appreciate it if you could double check it as well :)

Also, since this logic is getting a little bit more involved now, could you add a couple of unit test for findTrackComponent? It's fine to export the function just for testing purposes btw. Thanks!

(I don't want to overburden you with tests btw, I just want to keep stuff like this covered and understandable for everyone. So if you don't have the time, just let me know. We appreciate your hard work either way!)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted, unit and e2e tests added.

@Lms24Lms24 self-assigned this Sep 4, 2024
@Zen-cronic

Copy link
Copy Markdown
ContributorAuthor

Just read your comments and review, I'll take them up now. Thanks!

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I realized I approved the PR yesterday on accident. Please don't mind the change request, I'm just putting it here so that we don't merge the PR accidentally before the extract name logic is resolved.

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
*
*/
function extractComponentName(name: string): string {
return name.replace(/^<([^\s]*)>(?: at [^\s]*)?$/, '$1');

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on what you suggested @Lms24, just an added non-capture group. The ReDoS Checker passes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me, thanks!

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
'sentry.op': 'ui.vue.mount',
'sentry.origin': 'auto.ui.vue',
},
description: 'Vue <<HomeView>>',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just making sure, are the double brackets intended? They're formatted here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation! I don't think they're intended. Let's remove them.

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the changes! We're almost there :)

});
});

test('sends a lifecycle span for the tracked HomeView component - with `<>`', async ({ page }) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of an unfortunate and very intransparent limitation of our e2e test setup but we shouldn't navigate to the same route in more than one e2e test. The reason is that we execute them in parallel in CI and the waitForTransaction call from one test might actually resolve for a transaction in another test, depending on the condition in the callback.

To avoid this confusion, we generally create a new route for each test. Suggestion: Either we create new routes for the two new tests or we integrate them in already existing tests and simply check for the spans there. I'll let you make the call, I'm fine with either option.
Also, feel free to test both kinds trackComponent notations in one route/test :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, thanks for the info. i decided to go with creating a new route, and grouped the different notations together in a single test.

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
Group trackComponent tests together:
1. With <>
2. Without <>
3. Not tracked
Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
<h1>Demonstrating Component Tracking</h1>
<ComponentOneView />
<ComponentTwoView />
</template>

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inspired by the sveltekit-2-svelte-5 test setup.

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot @Zen-cronic, looks great to me now!

By the way, the entire JS SDK team really appreciates your contributions! @mydea sent you a message with a small thank you gift on LinkedIn a couple of days ago, in case you missed it. If this message somehow got lost, please feel free to drop me or @mydea an email (email in GH bio) :)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vue trackComponents allowlist should match for components without <>

2 participants

@Zen-cronic@Lms24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(vue): Update Vue trackComponents list to match components with or without <> - #13543

Merged
Lms24 merged 5 commits into
getsentry:developfrom
Zen-cronic:fix/trackComponents-allowList
Sep 9, 2024
Merged

fix(vue): Update Vue trackComponents list to match components with or without <>#13543
Lms24 merged 5 commits into
getsentry:developfrom
Zen-cronic:fix/trackComponents-allowList

Conversation

@Zen-cronic

Copy link
Copy Markdown
Contributor

Resolves#13510

Tests to be added.

  • If you've added code that should be tested, please add tests.
  • Ensure your code lints and the test suite passes (yarn lint) & (yarn test).

…or without `<>`
Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
Comment threadpackages/vue/src/tracing.ts Outdated
if (!(currentCompo.startsWith('<') && currentCompo.endsWith('>'))) {
currentCompo = `<${currentCompo}>`;
}
return formattedName === currentCompo;

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formattedName from formatComponentName() always return a name with < >.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, it can also be <Component> at Component.vue.

@Zen-cronic
Zen-cronic marked this pull request as ready for review September 3, 2024 01:00

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @Zen-cronic thanks for opening this PR!

The change looks good to me but I had a slightly more size-efficient solution.

Could you add a test as well? We already have a Vue 3 test E2E test application. I was thinking that we could set a trackComponents array option in the Sentry init call and adjust the pageload test that it also includes a ui.vue.* span for a component that matches one of the array entries.

If you don't have time, just let me know, then I'll add the test.

Comment threadpackages/vue/src/tracing.ts Outdated
Comment on lines +52 to +57
let currentCompo = compo;

if (!(currentCompo.startsWith('<') && currentCompo.endsWith('>'))) {
currentCompo = `<${currentCompo}>`;
}
return formattedName === currentCompo;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can replace this with a more bundle size efficient version:

Suggested change
letcurrentCompo=compo;
if(!(currentCompo.startsWith('<')&&currentCompo.endsWith('>'))){
currentCompo=`<${currentCompo}>`;
}
returnformattedName===currentCompo;
returncompo.replace(/^<(.*)>$/,'$1')===formattedName

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah in this case we don't even need the block anymore but could inline it to the some call. So feel free to do that as well :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, I've taken your approach and found that formattedName must also be replaced:

functionfindTrackComponent(trackComponents: string[],formattedName: string): boolean{functionextractComponentName(name: string): string{returnname.replace(/^<(.*)>(?:.*)?$/,"$1");}constisMatched=trackComponents.some(compo=>{returnextractComponentName(formattedName)===extractComponentName(compo)});returnisMatched;}

The regex is needed for both the user provided component and the formatted component.

  • User provided: either <App> or App
  • Formatted: either <App> or <App> at App.vue.

What do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation, you're right! Also, I didn't think about the <App> at App.vue case.

There's one unfortunate side effect of this regex though: It can lead to polynomial build up when evaluating an input. Given that we run extractComponentName on user input I'd rather not risk the (still unlikely but possible) chance of some kind of ReDoS attack (I used this ReDoS checker to test it)

I played around with the regex to make it safer and keep it linear and arrived at this one: /^<([^\s]*)>( at [^\s]*)?$
I think this should still cover all component names but would appreciate it if you could double check it as well :)

Also, since this logic is getting a little bit more involved now, could you add a couple of unit test for findTrackComponent? It's fine to export the function just for testing purposes btw. Thanks!

(I don't want to overburden you with tests btw, I just want to keep stuff like this covered and understandable for everyone. So if you don't have the time, just let me know. We appreciate your hard work either way!)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted, unit and e2e tests added.

@Lms24Lms24 self-assigned this Sep 4, 2024
@Zen-cronic

Copy link
Copy Markdown
ContributorAuthor

Just read your comments and review, I'll take them up now. Thanks!

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I realized I approved the PR yesterday on accident. Please don't mind the change request, I'm just putting it here so that we don't merge the PR accidentally before the extract name logic is resolved.

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
*
*/
function extractComponentName(name: string): string {
return name.replace(/^<([^\s]*)>(?: at [^\s]*)?$/, '$1');

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on what you suggested @Lms24, just an added non-capture group. The ReDoS Checker passes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me, thanks!

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
'sentry.op': 'ui.vue.mount',
'sentry.origin': 'auto.ui.vue',
},
description: 'Vue <<HomeView>>',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just making sure, are the double brackets intended? They're formatted here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation! I don't think they're intended. Let's remove them.

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the changes! We're almost there :)

});
});

test('sends a lifecycle span for the tracked HomeView component - with `<>`', async ({ page }) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of an unfortunate and very intransparent limitation of our e2e test setup but we shouldn't navigate to the same route in more than one e2e test. The reason is that we execute them in parallel in CI and the waitForTransaction call from one test might actually resolve for a transaction in another test, depending on the condition in the callback.

To avoid this confusion, we generally create a new route for each test. Suggestion: Either we create new routes for the two new tests or we integrate them in already existing tests and simply check for the spans there. I'll let you make the call, I'm fine with either option.
Also, feel free to test both kinds trackComponent notations in one route/test :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, thanks for the info. i decided to go with creating a new route, and grouped the different notations together in a single test.

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
Group trackComponent tests together:
1. With <>
2. Without <>
3. Not tracked
Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
<h1>Demonstrating Component Tracking</h1>
<ComponentOneView />
<ComponentTwoView />
</template>

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inspired by the sveltekit-2-svelte-5 test setup.

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot @Zen-cronic, looks great to me now!

By the way, the entire JS SDK team really appreciates your contributions! @mydea sent you a message with a small thank you gift on LinkedIn a couple of days ago, in case you missed it. If this message somehow got lost, please feel free to drop me or @mydea an email (email in GH bio) :)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vue trackComponents allowlist should match for components without <>

2 participants

@Zen-cronic@Lms24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(vue): Update Vue trackComponents list to match components with or without <> - #13543

Merged
Lms24 merged 5 commits into
getsentry:developfrom
Zen-cronic:fix/trackComponents-allowList
Sep 9, 2024
Merged

fix(vue): Update Vue trackComponents list to match components with or without <>#13543
Lms24 merged 5 commits into
getsentry:developfrom
Zen-cronic:fix/trackComponents-allowList

Conversation

@Zen-cronic

Copy link
Copy Markdown
Contributor

Resolves#13510

Tests to be added.

  • If you've added code that should be tested, please add tests.
  • Ensure your code lints and the test suite passes (yarn lint) & (yarn test).

…or without `<>`
Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
Comment threadpackages/vue/src/tracing.ts Outdated
if (!(currentCompo.startsWith('<') && currentCompo.endsWith('>'))) {
currentCompo = `<${currentCompo}>`;
}
return formattedName === currentCompo;

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formattedName from formatComponentName() always return a name with < >.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, it can also be <Component> at Component.vue.

@Zen-cronic
Zen-cronic marked this pull request as ready for review September 3, 2024 01:00

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @Zen-cronic thanks for opening this PR!

The change looks good to me but I had a slightly more size-efficient solution.

Could you add a test as well? We already have a Vue 3 test E2E test application. I was thinking that we could set a trackComponents array option in the Sentry init call and adjust the pageload test that it also includes a ui.vue.* span for a component that matches one of the array entries.

If you don't have time, just let me know, then I'll add the test.

Comment threadpackages/vue/src/tracing.ts Outdated
Comment on lines +52 to +57
let currentCompo = compo;

if (!(currentCompo.startsWith('<') && currentCompo.endsWith('>'))) {
currentCompo = `<${currentCompo}>`;
}
return formattedName === currentCompo;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can replace this with a more bundle size efficient version:

Suggested change
letcurrentCompo=compo;
if(!(currentCompo.startsWith('<')&&currentCompo.endsWith('>'))){
currentCompo=`<${currentCompo}>`;
}
returnformattedName===currentCompo;
returncompo.replace(/^<(.*)>$/,'$1')===formattedName

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah in this case we don't even need the block anymore but could inline it to the some call. So feel free to do that as well :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, I've taken your approach and found that formattedName must also be replaced:

functionfindTrackComponent(trackComponents: string[],formattedName: string): boolean{functionextractComponentName(name: string): string{returnname.replace(/^<(.*)>(?:.*)?$/,"$1");}constisMatched=trackComponents.some(compo=>{returnextractComponentName(formattedName)===extractComponentName(compo)});returnisMatched;}

The regex is needed for both the user provided component and the formatted component.

  • User provided: either <App> or App
  • Formatted: either <App> or <App> at App.vue.

What do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation, you're right! Also, I didn't think about the <App> at App.vue case.

There's one unfortunate side effect of this regex though: It can lead to polynomial build up when evaluating an input. Given that we run extractComponentName on user input I'd rather not risk the (still unlikely but possible) chance of some kind of ReDoS attack (I used this ReDoS checker to test it)

I played around with the regex to make it safer and keep it linear and arrived at this one: /^<([^\s]*)>( at [^\s]*)?$
I think this should still cover all component names but would appreciate it if you could double check it as well :)

Also, since this logic is getting a little bit more involved now, could you add a couple of unit test for findTrackComponent? It's fine to export the function just for testing purposes btw. Thanks!

(I don't want to overburden you with tests btw, I just want to keep stuff like this covered and understandable for everyone. So if you don't have the time, just let me know. We appreciate your hard work either way!)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted, unit and e2e tests added.

@Lms24Lms24 self-assigned this Sep 4, 2024
@Zen-cronic

Copy link
Copy Markdown
ContributorAuthor

Just read your comments and review, I'll take them up now. Thanks!

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I realized I approved the PR yesterday on accident. Please don't mind the change request, I'm just putting it here so that we don't merge the PR accidentally before the extract name logic is resolved.

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
*
*/
function extractComponentName(name: string): string {
return name.replace(/^<([^\s]*)>(?: at [^\s]*)?$/, '$1');

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on what you suggested @Lms24, just an added non-capture group. The ReDoS Checker passes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me, thanks!

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
'sentry.op': 'ui.vue.mount',
'sentry.origin': 'auto.ui.vue',
},
description: 'Vue <<HomeView>>',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just making sure, are the double brackets intended? They're formatted here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation! I don't think they're intended. Let's remove them.

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the changes! We're almost there :)

});
});

test('sends a lifecycle span for the tracked HomeView component - with `<>`', async ({ page }) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of an unfortunate and very intransparent limitation of our e2e test setup but we shouldn't navigate to the same route in more than one e2e test. The reason is that we execute them in parallel in CI and the waitForTransaction call from one test might actually resolve for a transaction in another test, depending on the condition in the callback.

To avoid this confusion, we generally create a new route for each test. Suggestion: Either we create new routes for the two new tests or we integrate them in already existing tests and simply check for the spans there. I'll let you make the call, I'm fine with either option.
Also, feel free to test both kinds trackComponent notations in one route/test :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, thanks for the info. i decided to go with creating a new route, and grouped the different notations together in a single test.

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
Group trackComponent tests together:
1. With <>
2. Without <>
3. Not tracked
Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
<h1>Demonstrating Component Tracking</h1>
<ComponentOneView />
<ComponentTwoView />
</template>

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inspired by the sveltekit-2-svelte-5 test setup.

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot @Zen-cronic, looks great to me now!

By the way, the entire JS SDK team really appreciates your contributions! @mydea sent you a message with a small thank you gift on LinkedIn a couple of days ago, in case you missed it. If this message somehow got lost, please feel free to drop me or @mydea an email (email in GH bio) :)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vue trackComponents allowlist should match for components without <>

2 participants

@Zen-cronic@Lms24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(vue): Update Vue trackComponents list to match components with or without <> - #13543

Merged
Lms24 merged 5 commits into
getsentry:developfrom
Zen-cronic:fix/trackComponents-allowList
Sep 9, 2024
Merged

fix(vue): Update Vue trackComponents list to match components with or without <>#13543
Lms24 merged 5 commits into
getsentry:developfrom
Zen-cronic:fix/trackComponents-allowList

Conversation

@Zen-cronic

Copy link
Copy Markdown
Contributor

Resolves#13510

Tests to be added.

  • If you've added code that should be tested, please add tests.
  • Ensure your code lints and the test suite passes (yarn lint) & (yarn test).

…or without `<>`
Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
Comment threadpackages/vue/src/tracing.ts Outdated
if (!(currentCompo.startsWith('<') && currentCompo.endsWith('>'))) {
currentCompo = `<${currentCompo}>`;
}
return formattedName === currentCompo;

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formattedName from formatComponentName() always return a name with < >.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, it can also be <Component> at Component.vue.

@Zen-cronic
Zen-cronic marked this pull request as ready for review September 3, 2024 01:00

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @Zen-cronic thanks for opening this PR!

The change looks good to me but I had a slightly more size-efficient solution.

Could you add a test as well? We already have a Vue 3 test E2E test application. I was thinking that we could set a trackComponents array option in the Sentry init call and adjust the pageload test that it also includes a ui.vue.* span for a component that matches one of the array entries.

If you don't have time, just let me know, then I'll add the test.

Comment threadpackages/vue/src/tracing.ts Outdated
Comment on lines +52 to +57
let currentCompo = compo;

if (!(currentCompo.startsWith('<') && currentCompo.endsWith('>'))) {
currentCompo = `<${currentCompo}>`;
}
return formattedName === currentCompo;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can replace this with a more bundle size efficient version:

Suggested change
letcurrentCompo=compo;
if(!(currentCompo.startsWith('<')&&currentCompo.endsWith('>'))){
currentCompo=`<${currentCompo}>`;
}
returnformattedName===currentCompo;
returncompo.replace(/^<(.*)>$/,'$1')===formattedName

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah in this case we don't even need the block anymore but could inline it to the some call. So feel free to do that as well :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, I've taken your approach and found that formattedName must also be replaced:

functionfindTrackComponent(trackComponents: string[],formattedName: string): boolean{functionextractComponentName(name: string): string{returnname.replace(/^<(.*)>(?:.*)?$/,"$1");}constisMatched=trackComponents.some(compo=>{returnextractComponentName(formattedName)===extractComponentName(compo)});returnisMatched;}

The regex is needed for both the user provided component and the formatted component.

  • User provided: either <App> or App
  • Formatted: either <App> or <App> at App.vue.

What do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation, you're right! Also, I didn't think about the <App> at App.vue case.

There's one unfortunate side effect of this regex though: It can lead to polynomial build up when evaluating an input. Given that we run extractComponentName on user input I'd rather not risk the (still unlikely but possible) chance of some kind of ReDoS attack (I used this ReDoS checker to test it)

I played around with the regex to make it safer and keep it linear and arrived at this one: /^<([^\s]*)>( at [^\s]*)?$
I think this should still cover all component names but would appreciate it if you could double check it as well :)

Also, since this logic is getting a little bit more involved now, could you add a couple of unit test for findTrackComponent? It's fine to export the function just for testing purposes btw. Thanks!

(I don't want to overburden you with tests btw, I just want to keep stuff like this covered and understandable for everyone. So if you don't have the time, just let me know. We appreciate your hard work either way!)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted, unit and e2e tests added.

@Lms24Lms24 self-assigned this Sep 4, 2024
@Zen-cronic

Copy link
Copy Markdown
ContributorAuthor

Just read your comments and review, I'll take them up now. Thanks!

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I realized I approved the PR yesterday on accident. Please don't mind the change request, I'm just putting it here so that we don't merge the PR accidentally before the extract name logic is resolved.

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
*
*/
function extractComponentName(name: string): string {
return name.replace(/^<([^\s]*)>(?: at [^\s]*)?$/, '$1');

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on what you suggested @Lms24, just an added non-capture group. The ReDoS Checker passes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me, thanks!

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
'sentry.op': 'ui.vue.mount',
'sentry.origin': 'auto.ui.vue',
},
description: 'Vue <<HomeView>>',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just making sure, are the double brackets intended? They're formatted here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation! I don't think they're intended. Let's remove them.

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the changes! We're almost there :)

});
});

test('sends a lifecycle span for the tracked HomeView component - with `<>`', async ({ page }) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of an unfortunate and very intransparent limitation of our e2e test setup but we shouldn't navigate to the same route in more than one e2e test. The reason is that we execute them in parallel in CI and the waitForTransaction call from one test might actually resolve for a transaction in another test, depending on the condition in the callback.

To avoid this confusion, we generally create a new route for each test. Suggestion: Either we create new routes for the two new tests or we integrate them in already existing tests and simply check for the spans there. I'll let you make the call, I'm fine with either option.
Also, feel free to test both kinds trackComponent notations in one route/test :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, thanks for the info. i decided to go with creating a new route, and grouped the different notations together in a single test.

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
Group trackComponent tests together:
1. With <>
2. Without <>
3. Not tracked
Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
<h1>Demonstrating Component Tracking</h1>
<ComponentOneView />
<ComponentTwoView />
</template>

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inspired by the sveltekit-2-svelte-5 test setup.

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot @Zen-cronic, looks great to me now!

By the way, the entire JS SDK team really appreciates your contributions! @mydea sent you a message with a small thank you gift on LinkedIn a couple of days ago, in case you missed it. If this message somehow got lost, please feel free to drop me or @mydea an email (email in GH bio) :)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vue trackComponents allowlist should match for components without <>

2 participants

@Zen-cronic@Lms24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(vue): Update Vue trackComponents list to match components with or without <> - #13543

Merged
Lms24 merged 5 commits into
getsentry:developfrom
Zen-cronic:fix/trackComponents-allowList
Sep 9, 2024
Merged

fix(vue): Update Vue trackComponents list to match components with or without <>#13543
Lms24 merged 5 commits into
getsentry:developfrom
Zen-cronic:fix/trackComponents-allowList

Conversation

@Zen-cronic

Copy link
Copy Markdown
Contributor

Resolves#13510

Tests to be added.

  • If you've added code that should be tested, please add tests.
  • Ensure your code lints and the test suite passes (yarn lint) & (yarn test).

…or without `<>`
Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
Comment threadpackages/vue/src/tracing.ts Outdated
if (!(currentCompo.startsWith('<') && currentCompo.endsWith('>'))) {
currentCompo = `<${currentCompo}>`;
}
return formattedName === currentCompo;

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formattedName from formatComponentName() always return a name with < >.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, it can also be <Component> at Component.vue.

@Zen-cronic
Zen-cronic marked this pull request as ready for review September 3, 2024 01:00

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @Zen-cronic thanks for opening this PR!

The change looks good to me but I had a slightly more size-efficient solution.

Could you add a test as well? We already have a Vue 3 test E2E test application. I was thinking that we could set a trackComponents array option in the Sentry init call and adjust the pageload test that it also includes a ui.vue.* span for a component that matches one of the array entries.

If you don't have time, just let me know, then I'll add the test.

Comment threadpackages/vue/src/tracing.ts Outdated
Comment on lines +52 to +57
let currentCompo = compo;

if (!(currentCompo.startsWith('<') && currentCompo.endsWith('>'))) {
currentCompo = `<${currentCompo}>`;
}
return formattedName === currentCompo;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can replace this with a more bundle size efficient version:

Suggested change
letcurrentCompo=compo;
if(!(currentCompo.startsWith('<')&&currentCompo.endsWith('>'))){
currentCompo=`<${currentCompo}>`;
}
returnformattedName===currentCompo;
returncompo.replace(/^<(.*)>$/,'$1')===formattedName

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah in this case we don't even need the block anymore but could inline it to the some call. So feel free to do that as well :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, I've taken your approach and found that formattedName must also be replaced:

functionfindTrackComponent(trackComponents: string[],formattedName: string): boolean{functionextractComponentName(name: string): string{returnname.replace(/^<(.*)>(?:.*)?$/,"$1");}constisMatched=trackComponents.some(compo=>{returnextractComponentName(formattedName)===extractComponentName(compo)});returnisMatched;}

The regex is needed for both the user provided component and the formatted component.

  • User provided: either <App> or App
  • Formatted: either <App> or <App> at App.vue.

What do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation, you're right! Also, I didn't think about the <App> at App.vue case.

There's one unfortunate side effect of this regex though: It can lead to polynomial build up when evaluating an input. Given that we run extractComponentName on user input I'd rather not risk the (still unlikely but possible) chance of some kind of ReDoS attack (I used this ReDoS checker to test it)

I played around with the regex to make it safer and keep it linear and arrived at this one: /^<([^\s]*)>( at [^\s]*)?$
I think this should still cover all component names but would appreciate it if you could double check it as well :)

Also, since this logic is getting a little bit more involved now, could you add a couple of unit test for findTrackComponent? It's fine to export the function just for testing purposes btw. Thanks!

(I don't want to overburden you with tests btw, I just want to keep stuff like this covered and understandable for everyone. So if you don't have the time, just let me know. We appreciate your hard work either way!)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted, unit and e2e tests added.

@Lms24Lms24 self-assigned this Sep 4, 2024
@Zen-cronic

Copy link
Copy Markdown
ContributorAuthor

Just read your comments and review, I'll take them up now. Thanks!

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I realized I approved the PR yesterday on accident. Please don't mind the change request, I'm just putting it here so that we don't merge the PR accidentally before the extract name logic is resolved.

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
*
*/
function extractComponentName(name: string): string {
return name.replace(/^<([^\s]*)>(?: at [^\s]*)?$/, '$1');

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on what you suggested @Lms24, just an added non-capture group. The ReDoS Checker passes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me, thanks!

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
'sentry.op': 'ui.vue.mount',
'sentry.origin': 'auto.ui.vue',
},
description: 'Vue <<HomeView>>',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just making sure, are the double brackets intended? They're formatted here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation! I don't think they're intended. Let's remove them.

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the changes! We're almost there :)

});
});

test('sends a lifecycle span for the tracked HomeView component - with `<>`', async ({ page }) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of an unfortunate and very intransparent limitation of our e2e test setup but we shouldn't navigate to the same route in more than one e2e test. The reason is that we execute them in parallel in CI and the waitForTransaction call from one test might actually resolve for a transaction in another test, depending on the condition in the callback.

To avoid this confusion, we generally create a new route for each test. Suggestion: Either we create new routes for the two new tests or we integrate them in already existing tests and simply check for the spans there. I'll let you make the call, I'm fine with either option.
Also, feel free to test both kinds trackComponent notations in one route/test :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, thanks for the info. i decided to go with creating a new route, and grouped the different notations together in a single test.

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
Group trackComponent tests together:
1. With <>
2. Without <>
3. Not tracked
Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
<h1>Demonstrating Component Tracking</h1>
<ComponentOneView />
<ComponentTwoView />
</template>

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inspired by the sveltekit-2-svelte-5 test setup.

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot @Zen-cronic, looks great to me now!

By the way, the entire JS SDK team really appreciates your contributions! @mydea sent you a message with a small thank you gift on LinkedIn a couple of days ago, in case you missed it. If this message somehow got lost, please feel free to drop me or @mydea an email (email in GH bio) :)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vue trackComponents allowlist should match for components without <>

2 participants

@Zen-cronic@Lms24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(vue): Update Vue trackComponents list to match components with or without <> - #13543

Merged
Lms24 merged 5 commits into
getsentry:developfrom
Zen-cronic:fix/trackComponents-allowList
Sep 9, 2024
Merged

fix(vue): Update Vue trackComponents list to match components with or without <>#13543
Lms24 merged 5 commits into
getsentry:developfrom
Zen-cronic:fix/trackComponents-allowList

Conversation

@Zen-cronic

Copy link
Copy Markdown
Contributor

Resolves#13510

Tests to be added.

  • If you've added code that should be tested, please add tests.
  • Ensure your code lints and the test suite passes (yarn lint) & (yarn test).

…or without `<>`
Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
Comment threadpackages/vue/src/tracing.ts Outdated
if (!(currentCompo.startsWith('<') && currentCompo.endsWith('>'))) {
currentCompo = `<${currentCompo}>`;
}
return formattedName === currentCompo;

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formattedName from formatComponentName() always return a name with < >.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, it can also be <Component> at Component.vue.

@Zen-cronic
Zen-cronic marked this pull request as ready for review September 3, 2024 01:00

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @Zen-cronic thanks for opening this PR!

The change looks good to me but I had a slightly more size-efficient solution.

Could you add a test as well? We already have a Vue 3 test E2E test application. I was thinking that we could set a trackComponents array option in the Sentry init call and adjust the pageload test that it also includes a ui.vue.* span for a component that matches one of the array entries.

If you don't have time, just let me know, then I'll add the test.

Comment threadpackages/vue/src/tracing.ts Outdated
Comment on lines +52 to +57
let currentCompo = compo;

if (!(currentCompo.startsWith('<') && currentCompo.endsWith('>'))) {
currentCompo = `<${currentCompo}>`;
}
return formattedName === currentCompo;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can replace this with a more bundle size efficient version:

Suggested change
letcurrentCompo=compo;
if(!(currentCompo.startsWith('<')&&currentCompo.endsWith('>'))){
currentCompo=`<${currentCompo}>`;
}
returnformattedName===currentCompo;
returncompo.replace(/^<(.*)>$/,'$1')===formattedName

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah in this case we don't even need the block anymore but could inline it to the some call. So feel free to do that as well :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, I've taken your approach and found that formattedName must also be replaced:

functionfindTrackComponent(trackComponents: string[],formattedName: string): boolean{functionextractComponentName(name: string): string{returnname.replace(/^<(.*)>(?:.*)?$/,"$1");}constisMatched=trackComponents.some(compo=>{returnextractComponentName(formattedName)===extractComponentName(compo)});returnisMatched;}

The regex is needed for both the user provided component and the formatted component.

  • User provided: either <App> or App
  • Formatted: either <App> or <App> at App.vue.

What do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation, you're right! Also, I didn't think about the <App> at App.vue case.

There's one unfortunate side effect of this regex though: It can lead to polynomial build up when evaluating an input. Given that we run extractComponentName on user input I'd rather not risk the (still unlikely but possible) chance of some kind of ReDoS attack (I used this ReDoS checker to test it)

I played around with the regex to make it safer and keep it linear and arrived at this one: /^<([^\s]*)>( at [^\s]*)?$
I think this should still cover all component names but would appreciate it if you could double check it as well :)

Also, since this logic is getting a little bit more involved now, could you add a couple of unit test for findTrackComponent? It's fine to export the function just for testing purposes btw. Thanks!

(I don't want to overburden you with tests btw, I just want to keep stuff like this covered and understandable for everyone. So if you don't have the time, just let me know. We appreciate your hard work either way!)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted, unit and e2e tests added.

@Lms24Lms24 self-assigned this Sep 4, 2024
@Zen-cronic

Copy link
Copy Markdown
ContributorAuthor

Just read your comments and review, I'll take them up now. Thanks!

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I realized I approved the PR yesterday on accident. Please don't mind the change request, I'm just putting it here so that we don't merge the PR accidentally before the extract name logic is resolved.

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
*
*/
function extractComponentName(name: string): string {
return name.replace(/^<([^\s]*)>(?: at [^\s]*)?$/, '$1');

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on what you suggested @Lms24, just an added non-capture group. The ReDoS Checker passes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me, thanks!

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
'sentry.op': 'ui.vue.mount',
'sentry.origin': 'auto.ui.vue',
},
description: 'Vue <<HomeView>>',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just making sure, are the double brackets intended? They're formatted here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation! I don't think they're intended. Let's remove them.

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the changes! We're almost there :)

});
});

test('sends a lifecycle span for the tracked HomeView component - with `<>`', async ({ page }) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of an unfortunate and very intransparent limitation of our e2e test setup but we shouldn't navigate to the same route in more than one e2e test. The reason is that we execute them in parallel in CI and the waitForTransaction call from one test might actually resolve for a transaction in another test, depending on the condition in the callback.

To avoid this confusion, we generally create a new route for each test. Suggestion: Either we create new routes for the two new tests or we integrate them in already existing tests and simply check for the spans there. I'll let you make the call, I'm fine with either option.
Also, feel free to test both kinds trackComponent notations in one route/test :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, thanks for the info. i decided to go with creating a new route, and grouped the different notations together in a single test.

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
Group trackComponent tests together:
1. With <>
2. Without <>
3. Not tracked
Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
<h1>Demonstrating Component Tracking</h1>
<ComponentOneView />
<ComponentTwoView />
</template>

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inspired by the sveltekit-2-svelte-5 test setup.

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot @Zen-cronic, looks great to me now!

By the way, the entire JS SDK team really appreciates your contributions! @mydea sent you a message with a small thank you gift on LinkedIn a couple of days ago, in case you missed it. If this message somehow got lost, please feel free to drop me or @mydea an email (email in GH bio) :)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vue trackComponents allowlist should match for components without <>

2 participants

@Zen-cronic@Lms24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(vue): Update Vue trackComponents list to match components with or without <> - #13543

Merged
Lms24 merged 5 commits into
getsentry:developfrom
Zen-cronic:fix/trackComponents-allowList
Sep 9, 2024
Merged

fix(vue): Update Vue trackComponents list to match components with or without <>#13543
Lms24 merged 5 commits into
getsentry:developfrom
Zen-cronic:fix/trackComponents-allowList

Conversation

@Zen-cronic

Copy link
Copy Markdown
Contributor

Resolves#13510

Tests to be added.

  • If you've added code that should be tested, please add tests.
  • Ensure your code lints and the test suite passes (yarn lint) & (yarn test).

…or without `<>`
Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
Comment threadpackages/vue/src/tracing.ts Outdated
if (!(currentCompo.startsWith('<') && currentCompo.endsWith('>'))) {
currentCompo = `<${currentCompo}>`;
}
return formattedName === currentCompo;

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formattedName from formatComponentName() always return a name with < >.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, it can also be <Component> at Component.vue.

@Zen-cronic
Zen-cronic marked this pull request as ready for review September 3, 2024 01:00

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @Zen-cronic thanks for opening this PR!

The change looks good to me but I had a slightly more size-efficient solution.

Could you add a test as well? We already have a Vue 3 test E2E test application. I was thinking that we could set a trackComponents array option in the Sentry init call and adjust the pageload test that it also includes a ui.vue.* span for a component that matches one of the array entries.

If you don't have time, just let me know, then I'll add the test.

Comment threadpackages/vue/src/tracing.ts Outdated
Comment on lines +52 to +57
let currentCompo = compo;

if (!(currentCompo.startsWith('<') && currentCompo.endsWith('>'))) {
currentCompo = `<${currentCompo}>`;
}
return formattedName === currentCompo;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can replace this with a more bundle size efficient version:

Suggested change
letcurrentCompo=compo;
if(!(currentCompo.startsWith('<')&&currentCompo.endsWith('>'))){
currentCompo=`<${currentCompo}>`;
}
returnformattedName===currentCompo;
returncompo.replace(/^<(.*)>$/,'$1')===formattedName

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah in this case we don't even need the block anymore but could inline it to the some call. So feel free to do that as well :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, I've taken your approach and found that formattedName must also be replaced:

functionfindTrackComponent(trackComponents: string[],formattedName: string): boolean{functionextractComponentName(name: string): string{returnname.replace(/^<(.*)>(?:.*)?$/,"$1");}constisMatched=trackComponents.some(compo=>{returnextractComponentName(formattedName)===extractComponentName(compo)});returnisMatched;}

The regex is needed for both the user provided component and the formatted component.

  • User provided: either <App> or App
  • Formatted: either <App> or <App> at App.vue.

What do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation, you're right! Also, I didn't think about the <App> at App.vue case.

There's one unfortunate side effect of this regex though: It can lead to polynomial build up when evaluating an input. Given that we run extractComponentName on user input I'd rather not risk the (still unlikely but possible) chance of some kind of ReDoS attack (I used this ReDoS checker to test it)

I played around with the regex to make it safer and keep it linear and arrived at this one: /^<([^\s]*)>( at [^\s]*)?$
I think this should still cover all component names but would appreciate it if you could double check it as well :)

Also, since this logic is getting a little bit more involved now, could you add a couple of unit test for findTrackComponent? It's fine to export the function just for testing purposes btw. Thanks!

(I don't want to overburden you with tests btw, I just want to keep stuff like this covered and understandable for everyone. So if you don't have the time, just let me know. We appreciate your hard work either way!)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted, unit and e2e tests added.

@Lms24Lms24 self-assigned this Sep 4, 2024
@Zen-cronic

Copy link
Copy Markdown
ContributorAuthor

Just read your comments and review, I'll take them up now. Thanks!

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I realized I approved the PR yesterday on accident. Please don't mind the change request, I'm just putting it here so that we don't merge the PR accidentally before the extract name logic is resolved.

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
*
*/
function extractComponentName(name: string): string {
return name.replace(/^<([^\s]*)>(?: at [^\s]*)?$/, '$1');

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on what you suggested @Lms24, just an added non-capture group. The ReDoS Checker passes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me, thanks!

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
'sentry.op': 'ui.vue.mount',
'sentry.origin': 'auto.ui.vue',
},
description: 'Vue <<HomeView>>',

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just making sure, are the double brackets intended? They're formatted here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation! I don't think they're intended. Let's remove them.

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the changes! We're almost there :)

});
});

test('sends a lifecycle span for the tracked HomeView component - with `<>`', async ({ page }) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of an unfortunate and very intransparent limitation of our e2e test setup but we shouldn't navigate to the same route in more than one e2e test. The reason is that we execute them in parallel in CI and the waitForTransaction call from one test might actually resolve for a transaction in another test, depending on the condition in the callback.

To avoid this confusion, we generally create a new route for each test. Suggestion: Either we create new routes for the two new tests or we integrate them in already existing tests and simply check for the spans there. I'll let you make the call, I'm fine with either option.
Also, feel free to test both kinds trackComponent notations in one route/test :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, thanks for the info. i decided to go with creating a new route, and grouped the different notations together in a single test.

Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
Group trackComponent tests together:
1. With <>
2. Without <>
3. Not tracked
Signed-off-by: Kaung Zin Hein <kaungzinhein113@gmail.com>
<h1>Demonstrating Component Tracking</h1>
<ComponentOneView />
<ComponentTwoView />
</template>

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inspired by the sveltekit-2-svelte-5 test setup.

@Lms24Lms24 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot @Zen-cronic, looks great to me now!

By the way, the entire JS SDK team really appreciates your contributions! @mydea sent you a message with a small thank you gift on LinkedIn a couple of days ago, in case you missed it. If this message somehow got lost, please feel free to drop me or @mydea an email (email in GH bio) :)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vue trackComponents allowlist should match for components without <>

2 participants

@Zen-cronic@Lms24