[web] workspace/sandpack: fallback bundler URL mechanism
This commit is contained in:
parent
efdf873922
commit
e5734dff71
|
|
@ -52,8 +52,8 @@ export default function InterviewsMarketingEmbedJavaScriptQuestion({
|
|||
language={language}
|
||||
nextQuestions={[]}
|
||||
question={javaScriptEmbedExample}
|
||||
sandpackO11yInstance="marketing.embed.js"
|
||||
similarQuestions={[]}
|
||||
timeoutLoggerInstance="marketing.embed.js"
|
||||
onLanguageChange={setLanguage}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -71,8 +71,8 @@ export default function InterviewsMarketingEmbedUIQuestion({
|
|||
mode="practice"
|
||||
nextQuestions={[]}
|
||||
question={question.frameworks[framework]}
|
||||
sandpackO11yInstance="marketing.embed.ui"
|
||||
similarQuestions={[]}
|
||||
timeoutLoggerInstance="marketing.embed.ui"
|
||||
onFrameworkChange={setFramework}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -8,22 +8,26 @@ import CodingPreferencesProvider from '~/components/global/CodingPreferencesProv
|
|||
import { useColorSchemePreferences } from '~/components/global/color-scheme/ColorSchemePreferencesProvider';
|
||||
import type { ProjectsChallengeSolutionBundle } from '~/components/projects/challenges/types';
|
||||
import SandpackObservability from '~/components/workspace/common/sandpack/SandpackObservability';
|
||||
import { useSandpackBundlerURL } from '~/components/workspace/common/sandpack/useSandpackBundlerURL';
|
||||
|
||||
import ProjectsChallengeSolutionWorkspace from './ProjectsChallengeSolutionWorkspace';
|
||||
import { useSandpackBundlerURL } from '~/components/workspace/common/sandpack/useSandpackBundlerURL';
|
||||
|
||||
type Props = Readonly<{
|
||||
solution: ProjectsChallengeSolutionBundle;
|
||||
}>;
|
||||
|
||||
const sandpackO11yInstance = 'projects.challenge_solution';
|
||||
|
||||
export default function ProjectsChallengeSolutionSection({ solution }: Props) {
|
||||
const { colorScheme } = useColorSchemePreferences();
|
||||
const bundlerURL = useSandpackBundlerURL();
|
||||
const bundlerURL = useSandpackBundlerURL(sandpackO11yInstance);
|
||||
const { files, workspace } = solution;
|
||||
|
||||
return (
|
||||
<CodingPreferencesProvider>
|
||||
<SandpackProvider
|
||||
// Remount if the bundler URL changes
|
||||
key={bundlerURL}
|
||||
customSetup={{
|
||||
environment: workspace.environment as SandboxEnvironment,
|
||||
}}
|
||||
|
|
@ -46,8 +50,8 @@ export default function ProjectsChallengeSolutionSection({ solution }: Props) {
|
|||
defaultFiles={files}
|
||||
/>
|
||||
<SandpackObservability
|
||||
instance="projects.official_solutions"
|
||||
bundlerURL={bundlerURL}
|
||||
instance={sandpackO11yInstance}
|
||||
/>
|
||||
</SandpackProvider>
|
||||
</CodingPreferencesProvider>
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ import logEvent from '~/logging/logEvent';
|
|||
import { getErrorMessage } from '~/utils/getErrorMessage';
|
||||
|
||||
type Props = Readonly<{
|
||||
// Identify which instance this happened. Should be unique within the page.
|
||||
instance?: string;
|
||||
// Bundler URL to ping for observability.
|
||||
bundlerURL: string;
|
||||
// Identify which instance this happened. Should be unique within the page.
|
||||
instance: string;
|
||||
}>;
|
||||
|
||||
function usePingSandpackBundler({ instance, bundlerURL }: Props) {
|
||||
function usePingSandpackBundler({ bundlerURL, instance }: Props) {
|
||||
const pingSentRef = useRef(false);
|
||||
const isUnloadingRef = useRef(false);
|
||||
|
||||
|
|
@ -62,20 +62,20 @@ function usePingSandpackBundler({ instance, bundlerURL }: Props) {
|
|||
return () => {
|
||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
};
|
||||
}, [instance]);
|
||||
}, [instance, bundlerURL]);
|
||||
}
|
||||
|
||||
const sandpackStartedEventName = 'sandpack-started';
|
||||
const sandpackReadyEventName = 'sandpack-ready';
|
||||
|
||||
export default function SandpackObservability({ instance, bundlerURL }: Props) {
|
||||
export default function SandpackObservability({ bundlerURL, instance }: Props) {
|
||||
const { sandpack } = useSandpack();
|
||||
const { status: sandpackStatus } = sandpack;
|
||||
const loadingStartedRef = useRef(false);
|
||||
const readySentRef = useRef(false);
|
||||
const timeoutSentRef = useRef(false);
|
||||
|
||||
usePingSandpackBundler({ instance, bundlerURL });
|
||||
usePingSandpackBundler({ bundlerURL, instance });
|
||||
|
||||
useEffect(() => {
|
||||
if (sandpackStatus === 'timeout' && !timeoutSentRef.current) {
|
||||
|
|
@ -94,7 +94,7 @@ export default function SandpackObservability({ instance, bundlerURL }: Props) {
|
|||
|
||||
loadingStartedRef.current = true;
|
||||
performance.mark(sandpackStartedEventName);
|
||||
}, []);
|
||||
}, [bundlerURL]);
|
||||
|
||||
useEffect(() => {
|
||||
if (sandpackStatus === 'running' && !readySentRef.current) {
|
||||
|
|
@ -114,7 +114,7 @@ export default function SandpackObservability({ instance, bundlerURL }: Props) {
|
|||
});
|
||||
readySentRef.current = true;
|
||||
}
|
||||
}, [instance, sandpackStatus]);
|
||||
}, [instance, sandpackStatus, bundlerURL]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,72 @@
|
|||
const defaultBundlerURL = 'https://bundler.greatfrontend.app';
|
||||
// const fallbackBundlerURL = 'https://bundler.greatfrontend.com';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
export function useSandpackBundlerURL(): string {
|
||||
return defaultBundlerURL;
|
||||
import { useGreatStorageLocal } from '~/hooks/useGreatStorageLocal';
|
||||
|
||||
import logEvent from '~/logging/logEvent';
|
||||
import { getErrorMessage } from '~/utils/getErrorMessage';
|
||||
|
||||
const defaultBundlerURL = 'https://bundler.greatfrontend.app';
|
||||
const fallbackBundlerURL = 'https://bundler.greatfrontend.com';
|
||||
|
||||
export function useSandpackBundlerURL(instance: string): string {
|
||||
const [url, setUrl] = useGreatStorageLocal(
|
||||
'workspace:bundler-url', // Change the key if you want to reset the URL in local storage
|
||||
defaultBundlerURL,
|
||||
{
|
||||
ttl: 24 * 60 * 60 * 7, // 7 days
|
||||
},
|
||||
);
|
||||
|
||||
const pingBundlerURL = useCallback(async () => {
|
||||
try {
|
||||
const response = await fetch(new URL('version.txt', url).toString());
|
||||
|
||||
await response.text();
|
||||
// Do nothing
|
||||
} catch (error: unknown) {
|
||||
const newUrl = fallbackBundlerURL;
|
||||
|
||||
// Change to fallback URL if default URL fails
|
||||
setUrl(newUrl);
|
||||
logEvent('sandpack.bundler_fallback', {
|
||||
error: getErrorMessage(error),
|
||||
instance,
|
||||
namespace: 'workspace',
|
||||
online: navigator.onLine,
|
||||
stack: error instanceof Error ? error.stack : null,
|
||||
url,
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch(new URL('version.txt', newUrl).toString());
|
||||
|
||||
await response.text();
|
||||
logEvent('sandpack.bundler_fallback.success', {
|
||||
instance,
|
||||
namespace: 'workspace',
|
||||
online: navigator.onLine,
|
||||
url: newUrl,
|
||||
});
|
||||
} catch (error_: unknown) {
|
||||
logEvent('sandpack.bundler_fallback.fail', {
|
||||
error: getErrorMessage(error_),
|
||||
instance,
|
||||
namespace: 'workspace',
|
||||
online: navigator.onLine,
|
||||
stack: error_ instanceof Error ? error_.stack : null,
|
||||
url: newUrl,
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [instance, setUrl, url]);
|
||||
|
||||
useEffect(() => {
|
||||
if (url !== defaultBundlerURL) {
|
||||
return;
|
||||
}
|
||||
|
||||
pingBundlerURL();
|
||||
}, [url, pingBundlerURL]);
|
||||
|
||||
return url;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ export default function JavaScriptCodingWorkspacePage({
|
|||
language={language}
|
||||
nextQuestions={nextQuestions}
|
||||
question={question}
|
||||
sandpackO11yInstance="workspace.js"
|
||||
similarQuestions={similarQuestions}
|
||||
studyListKey={studyListKey}
|
||||
timeoutLoggerInstance="workspace.js"
|
||||
onLanguageChange={setLanguage}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import type {
|
|||
QuestionJavaScript,
|
||||
QuestionMetadata,
|
||||
} from '~/components/interviews/questions/common/QuestionsTypes';
|
||||
|
||||
import JavaScriptCodingWorkspace from '~/components/workspace/javascript/JavaScriptCodingWorkspace';
|
||||
import { loadLocalJavaScriptQuestionCode } from '~/components/workspace/javascript/JavaScriptCodingWorkspaceCodeStorage';
|
||||
|
||||
|
|
@ -22,9 +21,9 @@ type Props = Readonly<{
|
|||
nextQuestions: ReadonlyArray<QuestionMetadata>;
|
||||
onLanguageChange: (language: QuestionCodingWorkingLanguage) => void;
|
||||
question: QuestionJavaScript;
|
||||
sandpackO11yInstance: string;
|
||||
similarQuestions: ReadonlyArray<QuestionMetadata>;
|
||||
studyListKey?: string;
|
||||
timeoutLoggerInstance: string;
|
||||
}>;
|
||||
|
||||
export default function JavaScriptCodingWorkspaceSection({
|
||||
|
|
@ -34,12 +33,12 @@ export default function JavaScriptCodingWorkspaceSection({
|
|||
nextQuestions,
|
||||
onLanguageChange,
|
||||
question,
|
||||
sandpackO11yInstance,
|
||||
similarQuestions,
|
||||
studyListKey,
|
||||
timeoutLoggerInstance,
|
||||
}: Props) {
|
||||
const { colorScheme } = useColorSchemePreferences();
|
||||
const bundlerURL = useSandpackBundlerURL();
|
||||
const bundlerURL = useSandpackBundlerURL(sandpackO11yInstance);
|
||||
|
||||
const { files, skeleton, workspace } = question;
|
||||
const loadedCode = loadLocalJavaScriptQuestionCode(
|
||||
|
|
@ -63,6 +62,8 @@ export default function JavaScriptCodingWorkspaceSection({
|
|||
return (
|
||||
<CodingPreferencesProvider>
|
||||
<SandpackProvider
|
||||
// Remount if the bundler URL changes
|
||||
key={bundlerURL}
|
||||
customSetup={{
|
||||
environment: 'parcel',
|
||||
}}
|
||||
|
|
@ -98,8 +99,8 @@ export default function JavaScriptCodingWorkspaceSection({
|
|||
onLanguageChange={onLanguageChange}
|
||||
/>
|
||||
<SandpackObservability
|
||||
instance={timeoutLoggerInstance}
|
||||
bundlerURL={bundlerURL}
|
||||
instance={sandpackO11yInstance}
|
||||
/>
|
||||
</SandpackProvider>
|
||||
</CodingPreferencesProvider>
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ export default function UserInterfaceCodingWorkspacePage({
|
|||
<UserInterfaceCodingWorkspaceSection
|
||||
{...props}
|
||||
embed={false}
|
||||
sandpackO11yInstance="workspace.ui"
|
||||
studyListKey={studyListKey}
|
||||
timeoutLoggerInstance="workspace.ui"
|
||||
onFrameworkChange={(value, contentType) => {
|
||||
const frameworkValue = value as QuestionFramework;
|
||||
const frameworkItem = metadata.frameworks.find(
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ import {
|
|||
questionUserInterfaceDescriptionPath,
|
||||
questionUserInterfaceSolutionPath,
|
||||
} from '~/components/interviews/questions/content/user-interface/QuestionUserInterfaceRoutes';
|
||||
import { useSandpackBundlerURL } from '~/components/workspace/common/sandpack/useSandpackBundlerURL';
|
||||
|
||||
import { useI18nRouter } from '~/next-i18nostic/src';
|
||||
|
||||
import SandpackObservability from '../common/sandpack/SandpackObservability';
|
||||
import UserInterfaceCodingWorkspace from './UserInterfaceCodingWorkspace';
|
||||
import { UserInterfaceCodingWorkspaceSavesContextProvider } from './UserInterfaceCodingWorkspaceSaveContext';
|
||||
import { useSandpackBundlerURL } from '~/components/workspace/common/sandpack/useSandpackBundlerURL';
|
||||
|
||||
type Props = Readonly<{
|
||||
canViewPremiumContent: boolean;
|
||||
|
|
@ -32,6 +32,8 @@ type Props = Readonly<{
|
|||
similarQuestions: ReadonlyArray<QuestionMetadata>;
|
||||
}>;
|
||||
|
||||
const sandpackO11yInstance = 'workspace.ui.saves';
|
||||
|
||||
export default function UserInterfaceCodingWorkspaceSavesPage({
|
||||
canViewPremiumContent,
|
||||
embed = false,
|
||||
|
|
@ -42,7 +44,7 @@ export default function UserInterfaceCodingWorkspaceSavesPage({
|
|||
}: Props) {
|
||||
const router = useI18nRouter();
|
||||
const { colorScheme } = useColorSchemePreferences();
|
||||
const bundlerURL = useSandpackBundlerURL();
|
||||
const bundlerURL = useSandpackBundlerURL(sandpackO11yInstance);
|
||||
|
||||
const { metadata, skeletonBundle } = question;
|
||||
const { files: defaultFiles, workspace } = skeletonBundle;
|
||||
|
|
@ -51,13 +53,15 @@ export default function UserInterfaceCodingWorkspaceSavesPage({
|
|||
<CodingPreferencesProvider>
|
||||
<UserInterfaceCodingWorkspaceSavesContextProvider save={save}>
|
||||
<SandpackProvider
|
||||
// Remount if the bundler URL changes
|
||||
key={bundlerURL}
|
||||
customSetup={{
|
||||
environment: workspace?.environment,
|
||||
}}
|
||||
files={JSON.parse(save.files)}
|
||||
options={{
|
||||
bundlerURL,
|
||||
activeFile: workspace?.activeFile,
|
||||
bundlerURL,
|
||||
classes: {
|
||||
'sp-input': 'touch-none select-none pointer-events-none',
|
||||
'sp-layout': 'h-full',
|
||||
|
|
@ -103,8 +107,8 @@ export default function UserInterfaceCodingWorkspaceSavesPage({
|
|||
}}
|
||||
/>
|
||||
<SandpackObservability
|
||||
instance="workspace.ui.saves"
|
||||
bundlerURL={bundlerURL}
|
||||
instance={sandpackO11yInstance}
|
||||
/>
|
||||
</SandpackProvider>
|
||||
</UserInterfaceCodingWorkspaceSavesContextProvider>
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@ import type {
|
|||
QuestionUserInterface,
|
||||
} from '~/components/interviews/questions/common/QuestionsTypes';
|
||||
import type { QuestionUserInterfaceMode } from '~/components/interviews/questions/common/QuestionUserInterfacePath';
|
||||
import { useSandpackBundlerURL } from '~/components/workspace/common/sandpack/useSandpackBundlerURL';
|
||||
import UserInterfaceCodingWorkspace from '~/components/workspace/user-interface/UserInterfaceCodingWorkspace';
|
||||
import { loadLocalUserInterfaceQuestionCode } from '~/components/workspace/user-interface/UserInterfaceCodingWorkspaceCodeStorage';
|
||||
|
||||
import SandpackObservability from '../common/sandpack/SandpackObservability';
|
||||
import { useSandpackBundlerURL } from '~/components/workspace/common/sandpack/useSandpackBundlerURL';
|
||||
|
||||
type Props = Readonly<{
|
||||
activeTabScrollIntoView?: boolean;
|
||||
|
|
@ -26,9 +26,9 @@ type Props = Readonly<{
|
|||
contentType: 'description' | 'solution',
|
||||
) => void;
|
||||
question: QuestionUserInterface;
|
||||
sandpackO11yInstance: string;
|
||||
similarQuestions: ReadonlyArray<QuestionMetadata>;
|
||||
studyListKey?: string;
|
||||
timeoutLoggerInstance: string;
|
||||
}>;
|
||||
|
||||
export default function UserInterfaceCodingWorkspaceSection({
|
||||
|
|
@ -39,12 +39,12 @@ export default function UserInterfaceCodingWorkspaceSection({
|
|||
nextQuestions,
|
||||
onFrameworkChange,
|
||||
question,
|
||||
sandpackO11yInstance,
|
||||
similarQuestions,
|
||||
studyListKey,
|
||||
timeoutLoggerInstance,
|
||||
}: Props) {
|
||||
const { colorScheme } = useColorSchemePreferences();
|
||||
const bundlerURL = useSandpackBundlerURL();
|
||||
const bundlerURL = useSandpackBundlerURL(sandpackO11yInstance);
|
||||
|
||||
const loadedFiles = loadLocalUserInterfaceQuestionCode(
|
||||
question,
|
||||
|
|
@ -69,6 +69,8 @@ export default function UserInterfaceCodingWorkspaceSection({
|
|||
return (
|
||||
<CodingPreferencesProvider>
|
||||
<SandpackProvider
|
||||
// Remount if the bundler URL changes
|
||||
key={bundlerURL}
|
||||
customSetup={{
|
||||
environment: workspace?.environment,
|
||||
}}
|
||||
|
|
@ -104,8 +106,8 @@ export default function UserInterfaceCodingWorkspaceSection({
|
|||
onFrameworkChange={onFrameworkChange}
|
||||
/>
|
||||
<SandpackObservability
|
||||
instance={timeoutLoggerInstance}
|
||||
bundlerURL={bundlerURL}
|
||||
instance={sandpackO11yInstance}
|
||||
/>
|
||||
</SandpackProvider>
|
||||
</CodingPreferencesProvider>
|
||||
|
|
|
|||
|
|
@ -8,22 +8,24 @@ import type { QuestionUserInterfaceBundle } from '~/components/interviews/questi
|
|||
import { useIntl } from '~/components/intl';
|
||||
import Anchor from '~/components/ui/Anchor';
|
||||
import Banner from '~/components/ui/Banner';
|
||||
import SandpackObservability from '~/components/workspace/common/sandpack/SandpackObservability';
|
||||
import { useSandpackBundlerURL } from '~/components/workspace/common/sandpack/useSandpackBundlerURL';
|
||||
|
||||
import UserInterfaceCodingWorkspacePreview from './UserInterfaceCodingWorkspacePreview';
|
||||
import useUserInterfaceCodingWorkspaceTilesContext from './useUserInterfaceCodingWorkspaceTilesContext';
|
||||
import SandpackObservability from '~/components/workspace/common/sandpack/SandpackObservability';
|
||||
import { useSandpackBundlerURL } from '~/components/workspace/common/sandpack/useSandpackBundlerURL';
|
||||
|
||||
type Props = Readonly<{
|
||||
bundle: QuestionUserInterfaceBundle;
|
||||
}>;
|
||||
|
||||
const sandpackO11yInstance = 'workspace.ui.solution_preview';
|
||||
|
||||
export default function UserInterfaceCodingWorkspaceSolutionPreviewTab({
|
||||
bundle,
|
||||
}: Props) {
|
||||
const intl = useIntl();
|
||||
const { colorScheme } = useColorSchemePreferences();
|
||||
const bundlerURL = useSandpackBundlerURL();
|
||||
const bundlerURL = useSandpackBundlerURL(sandpackO11yInstance);
|
||||
const { dispatch, getTabById } =
|
||||
useUserInterfaceCodingWorkspaceTilesContext();
|
||||
|
||||
|
|
@ -65,6 +67,8 @@ export default function UserInterfaceCodingWorkspaceSolutionPreviewTab({
|
|||
</Banner>
|
||||
<div className="flex h-0 grow">
|
||||
<SandpackProvider
|
||||
// Remount if the bundler URL changes
|
||||
key={bundlerURL}
|
||||
customSetup={{
|
||||
environment: workspace?.environment,
|
||||
}}
|
||||
|
|
@ -85,8 +89,8 @@ export default function UserInterfaceCodingWorkspaceSolutionPreviewTab({
|
|||
theme={colorScheme === 'dark' ? 'dark' : undefined}>
|
||||
<UserInterfaceCodingWorkspacePreview />
|
||||
<SandpackObservability
|
||||
instance="workspace.ui.solution_preview"
|
||||
bundlerURL={bundlerURL}
|
||||
instance={sandpackO11yInstance}
|
||||
/>
|
||||
</SandpackProvider>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ type LoggingAction =
|
|||
| 'question.run'
|
||||
| 'question.submit.complete'
|
||||
| 'question.submit'
|
||||
| 'sandpack.bundler_fallback.fail'
|
||||
| 'sandpack.bundler_fallback.success'
|
||||
| 'sandpack.bundler_fallback'
|
||||
| 'sandpack.reachable'
|
||||
| 'sandpack.running'
|
||||
| 'sandpack.timeout'
|
||||
|
|
|
|||
Loading…
Reference in New Issue