diff --git a/src/lib/client/adapters/webcontainer/index.js b/src/lib/client/adapters/webcontainer/index.js index eb204ca26..3b98cd5ca 100644 --- a/src/lib/client/adapters/webcontainer/index.js +++ b/src/lib/client/adapters/webcontainer/index.js @@ -4,6 +4,7 @@ import AnsiToHtml from 'ansi-to-html'; import * as yootils from 'yootils'; import { escape_html, get_depth } from '../../../utils.js'; import { ready } from '../common/index.js'; +import { isWebContainerSupported } from './utils.js'; /** * @typedef {import("../../../../routes/tutorial/[slug]/state.js").CompilerWarning} CompilerWarning @@ -25,8 +26,8 @@ let vm; * @returns {Promise} */ export async function create(base, error, progress, logs, warnings) { - if (/safari/i.test(navigator.userAgent) && !/chrome/i.test(navigator.userAgent)) { - throw new Error('WebContainers are not supported by Safari'); + if (!isWebContainerSupported()) { + throw new Error('WebContainers are not supported by Safari 16.3 or earlier'); } progress.set({ value: 0, text: 'loading files' }); diff --git a/src/lib/client/adapters/webcontainer/utils.js b/src/lib/client/adapters/webcontainer/utils.js new file mode 100644 index 000000000..1d6c6a569 --- /dev/null +++ b/src/lib/client/adapters/webcontainer/utils.js @@ -0,0 +1,30 @@ +/** + * Checks if WebContainer is supported on the current browser. + * This function is borrowed from [stackblitz/webcontainer-docs](https://github.com/stackblitz/webcontainer-docs/blob/369dd58b2749b085ed7642f22108a9bcbcd68fc4/docs/.vitepress/theme/components/Examples/WCEmbed/utils.ts#L4-L29) + */ +export function isWebContainerSupported() { + const hasSharedArrayBuffer = 'SharedArrayBuffer' in window; + const looksLikeChrome = navigator.userAgent.toLowerCase().includes('chrome'); + const looksLikeFirefox = navigator.userAgent.includes('Firefox'); + const looksLikeSafari = navigator.userAgent.includes('Safari'); + + if (hasSharedArrayBuffer && (looksLikeChrome || looksLikeFirefox)) { + return true; + } + + if (hasSharedArrayBuffer && looksLikeSafari) { + // we only support Safari 16.4 and up so we check for the version here + const match = navigator.userAgent.match(/Version\/(\d+)\.(\d+) (?:Mobile\/.*?)?Safari/); + const majorVersion = match ? Number(match?.[1]) : 0; + const minorVersion = match ? Number(match?.[2]) : 0; + + return majorVersion > 16 || (majorVersion === 16 && minorVersion >= 4); + } + + // Allow overriding the support check with localStorage.webcontainer_any_ua = 1 + try { + return Boolean(localStorage.getItem('webcontainer_any_ua')); + } catch { + return false; + } +} diff --git a/src/routes/tutorial/[slug]/Loading.svelte b/src/routes/tutorial/[slug]/Loading.svelte index cddea05a3..7563a6514 100644 --- a/src/routes/tutorial/[slug]/Loading.svelte +++ b/src/routes/tutorial/[slug]/Loading.svelte @@ -1,4 +1,6 @@