-
-
Notifications
You must be signed in to change notification settings - Fork 360
feat(playground): Open Sentry in desktop browser from Expo apps #5947
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
ba5474e
feat(playground): Open Sentry in desktop browser
krystofwoldrich e830880
fix(playground): Harden open-url middleware and add tests
antonis 44daef3
docs(changelog): Add changelog for playground open-url in Expo
antonis b336ece
refactor(playground): Make SENTRY_MIDDLEWARE_PATH non-exported
antonis 86b0af9
fix(playground): Only auto-open sentry.io URLs, log others to console
antonis c4514ee
fix(playground): Handle ESM open package, sanitize logged URLs
antonis 9063607
Merge branch 'main' into feat/playground-open-url-expo
antonis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| const SENTRY_MIDDLEWARE_PATH = '__sentry'; | ||
| export const SENTRY_CONTEXT_REQUEST_PATH = `${SENTRY_MIDDLEWARE_PATH}/context`; | ||
| export const SENTRY_OPEN_URL_REQUEST_PATH = `${SENTRY_MIDDLEWARE_PATH}/open-url`; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import type { IncomingMessage } from 'http'; | ||
|
|
||
| /** | ||
| * Get the raw body of a request. | ||
| */ | ||
| export function getRawBody(request: IncomingMessage): Promise<string> { | ||
| return new Promise((resolve, reject) => { | ||
| let data = ''; | ||
| request.on('data', chunk => { | ||
| data += chunk; | ||
| }); | ||
| request.on('end', () => { | ||
| resolve(data); | ||
| }); | ||
| request.on('error', reject); | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { debug } from '@sentry/core'; | ||
|
|
||
| import { getDevServer } from '../integrations/debugsymbolicatorutils'; | ||
| import { SENTRY_OPEN_URL_REQUEST_PATH } from './constants'; | ||
|
|
||
| /** | ||
| * Send request to the Metro Development Server Middleware to open a URL in the system browser. | ||
| */ | ||
| export function openURLInBrowser(url: string): void { | ||
| // oxlint-disable-next-line typescript-eslint(no-floating-promises) | ||
| fetch(`${getDevServer()?.url || '/'}${SENTRY_OPEN_URL_REQUEST_PATH}`, { | ||
| method: 'POST', | ||
| body: JSON.stringify({ url }), | ||
| }).catch(e => { | ||
| debug.error('Error opening URL:', e); | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| import type { IncomingMessage, ServerResponse } from 'http'; | ||
|
|
||
| import { getRawBody } from './getRawBody'; | ||
|
|
||
| /* | ||
| * Prefix for Sentry Metro logs to make them stand out to the user. | ||
| */ | ||
| const S = '\u001b[45;1m SENTRY \u001b[0m'; | ||
|
|
||
| let open: ((url: string) => Promise<void>) | undefined = undefined; | ||
|
|
||
| /** | ||
| * Open a URL in the system browser. | ||
| * | ||
| * Inspired by https://github.com/react-native-community/cli/blob/a856ce027a6b25f9363a8689311cdd4416c0fc89/packages/cli-server-api/src/openURLMiddleware.ts#L17 | ||
| */ | ||
| export async function openURLMiddleware(req: IncomingMessage, res: ServerResponse): Promise<void> { | ||
| if (req.method !== 'POST') { | ||
| res.writeHead(405); | ||
| res.end('Method not allowed. Use POST.'); | ||
| return; | ||
| } | ||
|
|
||
| if (!open) { | ||
| try { | ||
| // oxlint-disable-next-line import/no-extraneous-dependencies | ||
| const imported = require('open'); | ||
| // Handle both CJS (`module.exports = fn`) and ESM default export (`{ default: fn }`) | ||
| // oxlint-disable-next-line typescript-eslint(no-unsafe-member-access) | ||
| open = typeof imported === 'function' ? imported : imported?.default; | ||
| } catch (e) { | ||
| // noop | ||
| } | ||
| } | ||
|
|
||
| const body = await getRawBody(req); | ||
| let url: string | undefined = undefined; | ||
|
|
||
| try { | ||
| const parsedBody = JSON.parse(body) as { url?: string }; | ||
| url = parsedBody.url; | ||
| } catch (e) { | ||
| res.writeHead(400); | ||
| res.end('Invalid request body. Expected a JSON object with a url key.'); | ||
| return; | ||
| } | ||
|
|
||
| if (!url) { | ||
| res.writeHead(400); | ||
| res.end('Invalid request body. Expected a JSON object with a url key.'); | ||
| return; | ||
| } | ||
|
|
||
| if (!url.startsWith('https://') && !url.startsWith('http://')) { | ||
| res.writeHead(400); | ||
| res.end('Invalid URL scheme. Only http:// and https:// URLs are allowed.'); | ||
| return; | ||
| } | ||
|
|
||
| if (!isTrustedSentryHost(url)) { | ||
| // oxlint-disable-next-line no-console | ||
| console.log( | ||
| `${S} Untrusted host, not opening automatically. Open manually if you trust this URL: ${sanitizeForLog(url)}`, | ||
| ); | ||
| res.writeHead(200); | ||
| res.end(); | ||
| return; | ||
| } | ||
|
|
||
| if (!open) { | ||
| // oxlint-disable-next-line no-console | ||
| console.log(`${S} Could not open URL automatically. Open manually: ${sanitizeForLog(url)}`); | ||
| res.writeHead(500); | ||
| res.end('Failed to open URL. The "open" package is not available. Install it or open the URL manually.'); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| await open(url); | ||
| } catch (e) { | ||
| // oxlint-disable-next-line no-console | ||
| console.log(`${S} Failed to open URL automatically. Open manually: ${sanitizeForLog(url)}`); | ||
| res.writeHead(500); | ||
| res.end('Failed to open URL.'); | ||
| return; | ||
| } | ||
|
|
||
| // oxlint-disable-next-line no-console | ||
| console.log(`${S} Opened URL: ${sanitizeForLog(url)}`); | ||
| res.writeHead(200); | ||
| res.end(); | ||
| } | ||
|
|
||
| /** | ||
| * Strip control characters to prevent terminal escape sequence injection when logging URLs. | ||
| */ | ||
| function sanitizeForLog(value: string): string { | ||
| // oxlint-disable-next-line no-control-regex | ||
| return value.replace(/[\x00-\x1f\x7f]/g, ''); | ||
| } | ||
|
|
||
| function isTrustedSentryHost(url: string): boolean { | ||
| try { | ||
| const { hostname } = new URL(url); | ||
| return hostname === 'sentry.io' || hostname.endsWith('.sentry.io'); | ||
| } catch (e) { | ||
| return false; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd only allow requests that has the host
sentry.ioto avoid any malicious code invoking any kind of url with this method.This impact self-hosted users, but it's worth the risk,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder what's the best way to approach that. Technically we shouldn't exclude those using self-hosted Sentry.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An idea: if the host is not sentry.io then instead of automatically opening the link gets printed to the console with the note that you should only open it if you trust the host (so we basically jump to what's on lines 58-61)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch @lucas-zimerman and thank you for the suggestion @alwx ๐
Applied it with ec8663b