Skip to content

Commit

Permalink
Fix: Sidecar URL issues fixed across spotlight (#558)
Browse files Browse the repository at this point in the history
Fixes #524
  • Loading branch information
Shubhdeep12 authored Nov 14, 2024
1 parent aba5c07 commit 1749cb3
Show file tree
Hide file tree
Showing 12 changed files with 70 additions and 25 deletions.
8 changes: 8 additions & 0 deletions .changeset/twenty-fireants-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@spotlightjs/spotlight': minor
'@spotlightjs/overlay': minor
'@spotlightjs/sidecar': minor
---

- Sidecar url made generic to support all sidecar server routes.
- No use of static sidecar url.
5 changes: 2 additions & 3 deletions packages/overlay/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,9 @@ export default function App({
// See https://github.com/remix-run/react-router/issues/7634
const navigate = useNavigate();
const clearEvents = useCallback(async () => {
const { origin } = new URL(sidecarUrl);
const clearEventsUrl: string = `${origin}/clear`;

try {
const clearEventsUrl: string = new URL('/clear', sidecarUrl).href;

await db.reset();
await fetch(clearEventsUrl, {
method: 'DELETE',
Expand Down
9 changes: 6 additions & 3 deletions packages/overlay/src/components/OpenInEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import type React from 'react';
import { useCallback } from 'react';
import { useSpotlightContext } from '~/lib/useSpotlightContext';
import { ReactComponent as PenIcon } from '../assets/pen.svg';

export default function OpenInEditor({ file }: { file: string }) {
const { getSidecarUrl } = useSpotlightContext();
const sidecarOpenUrl: string = getSidecarUrl('/open');

const openInEditor = useCallback(
(evt: React.MouseEvent) => {
// TODO: Make this URL dynamic based on sidecarUrl!
fetch('http://localhost:8969/open', {
fetch(sidecarOpenUrl, {
method: 'POST',
body: file,
credentials: 'omit',
});
evt.stopPropagation();
},
[file],
[file, sidecarOpenUrl],
);
return (
<PenIcon
Expand Down
3 changes: 2 additions & 1 deletion packages/overlay/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const DEFAULT_SIDECAR_URL = 'http://localhost:8969/stream';
export const DEFAULT_SIDECAR_URL = 'http://localhost:8969';
export const DEFAULT_SIDECAR_STREAM_URL = new URL('/stream', DEFAULT_SIDECAR_URL).href;

export const DEFAULT_EXPERIMENTS = {
'sentry:focus-local-events': true,
Expand Down
11 changes: 7 additions & 4 deletions packages/overlay/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CONTEXT_LINES_ENDPOINT } from '@spotlightjs/sidecar/constants';
import { MemoryRouter } from 'react-router-dom';
import colors from 'tailwindcss/colors';
import App from './App';
import { DEFAULT_ANCHOR, DEFAULT_EXPERIMENTS, DEFAULT_SIDECAR_URL } from './constants';
import { DEFAULT_ANCHOR, DEFAULT_EXPERIMENTS, DEFAULT_SIDECAR_STREAM_URL } from './constants';
import globalStyles from './index.css?inline';
import { initIntegrations, type SpotlightContext } from './integrations/integration';
import { default as sentry } from './integrations/sentry/index';
Expand All @@ -12,6 +12,7 @@ import { activateLogger, log } from './lib/logger';
import { SpotlightContextProvider } from './lib/useSpotlightContext';
import { React, ReactDOM } from './react-instance'; // Import specific exports
import type { SpotlightOverlayOptions, WindowWithSpotlight } from './types';
import { removeURLSuffix } from './utils/remvoveURLSuffix';

export { default as console } from './integrations/console/index';
export { default as hydrationError } from './integrations/hydration-error/index';
Expand All @@ -22,7 +23,7 @@ export {
CONTEXT_LINES_ENDPOINT,
DEFAULT_ANCHOR,
DEFAULT_EXPERIMENTS,
DEFAULT_SIDECAR_URL,
DEFAULT_SIDECAR_STREAM_URL as DEFAULT_SIDECAR_URL,
off,
on,
React,
Expand Down Expand Up @@ -107,7 +108,7 @@ export async function init(options: SpotlightOverlayOptions = {}) {

const {
openOnInit = false,
sidecarUrl = DEFAULT_SIDECAR_URL,
sidecarUrl = DEFAULT_SIDECAR_STREAM_URL,
anchor = DEFAULT_ANCHOR,
integrations,
experiments = DEFAULT_EXPERIMENTS,
Expand All @@ -117,6 +118,7 @@ export async function init(options: SpotlightOverlayOptions = {}) {
} = options;

const isLoadedFromSidecar = new URL(sidecarUrl).origin === document.location.origin;
const sidecarBaseUrl: string = removeURLSuffix(sidecarUrl, '/stream');

const fullPage = options.fullPage ?? isLoadedFromSidecar;
const showTriggerButton = options.showTriggerButton ?? !fullPage;
Expand All @@ -130,12 +132,13 @@ export async function init(options: SpotlightOverlayOptions = {}) {
const finalExperiments = { ...DEFAULT_EXPERIMENTS, ...experiments };

// Sentry is enabled by default
const defaultIntegrations = () => [sentry({ sidecarUrl })];
const defaultIntegrations = () => [sentry()];

const context: SpotlightContext = {
open: openSpotlight,
close: closeSpotlight,
experiments: finalExperiments,
sidecarUrl: sidecarBaseUrl,
};

const [initializedIntegrations] = await initIntegrations(integrations ?? defaultIntegrations(), context);
Expand Down
1 change: 1 addition & 0 deletions packages/overlay/src/integrations/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type SpotlightContext = {
open: (path: string | undefined) => void;
close: () => void;
experiments: ExperimentsConfig;
sidecarUrl: string;
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class SentryDataCache {

protected subscribers: Map<string, Subscription> = new Map();

protected contextLinesProvider: string = new URL(DEFAULT_SIDECAR_URL).origin + CONTEXT_LINES_ENDPOINT;
protected contextLinesProvider: string = new URL(CONTEXT_LINES_ENDPOINT, DEFAULT_SIDECAR_URL).href;

constructor(
initial: (SentryEvent & {
Expand All @@ -45,8 +45,8 @@ class SentryDataCache {
}

setSidecarUrl(url: string) {
const { origin } = new URL(url);
this.contextLinesProvider = origin + CONTEXT_LINES_ENDPOINT;
const { href: contextLinesProviderUrl } = new URL(CONTEXT_LINES_ENDPOINT, url);
this.contextLinesProvider = contextLinesProviderUrl;
}

inferSdkFromEvent(event: SentryEvent) {
Expand Down
8 changes: 4 additions & 4 deletions packages/overlay/src/integrations/sentry/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Client, Envelope, EnvelopeItem } from '@sentry/types';
import { removeURLSuffix } from '~/utils/remvoveURLSuffix';
import { off, on } from '../../lib/eventTarget';
import { log, warn } from '../../lib/logger';
import type { Integration, RawEventContext } from '../integration';
Expand All @@ -14,7 +15,6 @@ import type { SentryErrorEvent, SentryEvent } from './types';
const HEADER = 'application/x-sentry-envelope';

type SentryIntegrationOptions = {
sidecarUrl?: string;
injectIntoSDK?: boolean;
openLastError?: boolean;
retries?: number;
Expand All @@ -25,12 +25,12 @@ export default function sentryIntegration(options: SentryIntegrationOptions = {}
name: 'sentry',
forwardedContentType: [HEADER],

setup: ({ open }) => {
setup: ({ open, sidecarUrl }) => {
if (options.retries == null) {
options.retries = 3;
}
if (options.sidecarUrl) {
sentryDataCache.setSidecarUrl(options.sidecarUrl);
if (sidecarUrl) {
sentryDataCache.setSidecarUrl(removeURLSuffix(sidecarUrl, '/stream'));
}
addSpotlightIntegrationToSentry(options);

Expand Down
9 changes: 7 additions & 2 deletions packages/overlay/src/lib/useSpotlightContext.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { createContext, useContext, type ReactNode } from 'react';
import { DEFAULT_EXPERIMENTS } from '../constants';
import { DEFAULT_EXPERIMENTS, DEFAULT_SIDECAR_URL } from '../constants';
import { type SpotlightContext } from '../integrations/integration';

const Context = createContext<SpotlightContext>({
open: () => {},
close: () => {},
experiments: DEFAULT_EXPERIMENTS,
sidecarUrl: DEFAULT_SIDECAR_URL,
});

export const SpotlightContextProvider: React.FC<{
Expand All @@ -16,5 +17,9 @@ export const SpotlightContextProvider: React.FC<{
};

export const useSpotlightContext = () => {
return useContext(Context);
const context = useContext(Context);
const getSidecarUrl = (path = '') => {
return new URL(path, context.sidecarUrl).href;
};
return { ...context, getSidecarUrl };
};
3 changes: 2 additions & 1 deletion packages/overlay/src/sidecar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export function connectToSidecar(
setOnline: React.Dispatch<React.SetStateAction<boolean>>,
): () => void {
log('Connecting to sidecar at', sidecarUrl);
const source = new EventSource(sidecarUrl);
const sidecarStreamUrl: string = new URL('/stream', sidecarUrl).href;
const source = new EventSource(sidecarStreamUrl);

for (const [contentType, listener] of Object.entries(contentTypeListeners)) {
source.addEventListener(contentType, listener);
Expand Down
17 changes: 17 additions & 0 deletions packages/overlay/src/utils/remvoveURLSuffix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export function removeURLSuffix(url: string, suffix: string): string {
try {
const parsedUrl = new URL(url);

if (parsedUrl.pathname.endsWith(suffix)) {
parsedUrl.pathname = parsedUrl.pathname.slice(0, -suffix.length);
}

return parsedUrl.toString();
} catch (error: unknown) {
if (error instanceof TypeError) {
throw new Error(`Invalid URL provided: ${url}. Error: ${error.message}`);
} else {
throw new Error(`An unexpected error occurred: ${String(error)}`);
}
}
}
15 changes: 11 additions & 4 deletions packages/spotlight/src/vite-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ const serverOptions = new Set(['importPath', 'integrationNames', 'port']);

export function buildClientInit(options: SpotlightInitOptions) {
const clientOptions = Object.fromEntries(
Object.entries(options).filter(([key, _value]) => !key.startsWith('_') && !serverOptions.has(key)),
Object.entries(options).filter(([key]) => !key.startsWith('_') && !serverOptions.has(key)),
);
let initOptions = JSON.stringify({
...clientOptions,
showTriggerButton: options.showTriggerButton !== false,
injectImmediately: options.injectImmediately !== false,
});

const integrationOptions = JSON.stringify({ sidecarUrl: options.sidecarUrl, openLastError: true });
const integrationOptions = JSON.stringify({ openLastError: true });
const integrations = (options.integrationNames || ['sentry']).map(i => `Spotlight.${i}(${integrationOptions})`);
initOptions = `{integrations: [${integrations.join(', ')}], ${initOptions.slice(1)}`;

Expand All @@ -72,6 +72,13 @@ async function sendErrorToSpotlight(err: ErrorPayload['err'], spotlightUrl: stri
const errorLineInContext = contextLines?.indexOf(errorLine);
const event_id = randomBytes(16).toString('hex');
const timestamp = new Date();

const parsedUrl = new URL(spotlightUrl);
let spotlightErrorStreamUrl: string = spotlightUrl;
if (!parsedUrl.pathname.endsWith('/stream')) {
spotlightErrorStreamUrl = new URL('/stream', spotlightUrl).href;
}

const envelope = [
{ event_id, sent_at: timestamp.toISOString() },
{ type: 'event' },
Expand Down Expand Up @@ -117,7 +124,7 @@ async function sendErrorToSpotlight(err: ErrorPayload['err'], spotlightUrl: stri
]
.map(p => JSON.stringify(p))
.join('\n');
return await fetch(spotlightUrl, {
return await fetch(spotlightErrorStreamUrl, {
method: 'POST',
body: envelope,
headers: { 'Content-Type': 'application/x-sentry-envelope' },
Expand Down Expand Up @@ -151,7 +158,7 @@ export default function spotlight(options: SpotlightInitOptions = {}): PluginOpt
res: ServerResponse,
next: Connect.NextFunction,
) {
await sendErrorToSpotlight(err);
await sendErrorToSpotlight(err, options.sidecarUrl);

// The following part is per https://expressjs.com/en/guide/error-handling.html#the-default-error-handler
if (res.headersSent) {
Expand Down

0 comments on commit 1749cb3

Please sign in to comment.