diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7aecd35bd..03c74c06c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -777,6 +777,12 @@ importers: astro: specifier: ^5.1.9 version: 5.1.9(@types/node@22.10.10)(jiti@2.4.2)(lightningcss@1.29.1)(rollup@2.79.2)(terser@5.37.0)(typescript@5.7.3)(yaml@2.7.0) + base64url-universal: + specifier: ^2.0.0 + version: 2.0.0 + brotli-wasm: + specifier: ^3.0.1 + version: 3.0.1 claviature: specifier: ^0.1.0 version: 0.1.0 @@ -819,6 +825,9 @@ importers: tailwindcss: specifier: ^3.4.17 version: 3.4.17 + vite-plugin-wasm: + specifier: ^3.4.1 + version: 3.4.1(vite@6.0.11(@types/node@22.10.10)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0)) worker-timers: specifier: ^8.0.13 version: 8.0.13 @@ -3206,6 +3215,14 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + base64url-universal@2.0.0: + resolution: {integrity: sha512-6Hpg7EBf3t148C3+fMzjf+CHnADVDafWzlJUXAqqqbm4MKNXbsoPdOkWeRTjNlkYG7TpyjIpRO1Gk0SnsFD1rw==} + engines: {node: '>=14'} + + base64url@3.0.1: + resolution: {integrity: sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==} + engines: {node: '>=6.0.0'} + before-after-hook@2.2.3: resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} @@ -3237,6 +3254,10 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + brotli-wasm@3.0.1: + resolution: {integrity: sha512-U3K72/JAi3jITpdhZBqzSUq+DUY697tLxOuFXB+FpAE/Ug+5C3VZrv4uA674EUZHxNAuQ9wETXNqQkxZD6oL4A==} + engines: {node: '>=v18.0.0'} + browserslist@4.24.4: resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -5714,6 +5735,7 @@ packages: node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead node-fetch-native@1.6.6: resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==} @@ -7453,6 +7475,11 @@ packages: '@vite-pwa/assets-generator': optional: true + vite-plugin-wasm@3.4.1: + resolution: {integrity: sha512-ja3nSo2UCkVeitltJGkS3pfQHAanHv/DqGatdI39ja6McgABlpsZ5hVgl6wuR8Qx5etY3T5qgDQhOWzc5RReZA==} + peerDependencies: + vite: ^2 || ^3 || ^4 || ^5 || ^6 + vite@6.0.11: resolution: {integrity: sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -7657,6 +7684,7 @@ packages: workbox-google-analytics@7.0.0: resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==} + deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained workbox-navigation-preload@7.0.0: resolution: {integrity: sha512-juWCSrxo/fiMz3RsvDspeSLGmbgC0U9tKqcUPZBCf35s64wlaLXyn2KdHHXVQrb2cqF7I0Hc9siQalainmnXJA==} @@ -10797,6 +10825,12 @@ snapshots: base64-js@1.5.1: {} + base64url-universal@2.0.0: + dependencies: + base64url: 3.0.1 + + base64url@3.0.1: {} + before-after-hook@2.2.3: {} bin-links@4.0.4: @@ -10840,6 +10874,8 @@ snapshots: dependencies: fill-range: 7.1.1 + brotli-wasm@3.0.1: {} + browserslist@4.24.4: dependencies: caniuse-lite: 1.0.30001695 @@ -12762,7 +12798,7 @@ snapshots: jake@10.9.2: dependencies: async: 3.2.6 - chalk: 4.1.0 + chalk: 4.1.2 filelist: 1.0.4 minimatch: 3.1.2 @@ -15894,6 +15930,10 @@ snapshots: transitivePeerDependencies: - supports-color + vite-plugin-wasm@3.4.1(vite@6.0.11(@types/node@22.10.10)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0)): + dependencies: + vite: 6.0.11(@types/node@22.10.10)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0) + vite@6.0.11(@types/node@22.10.10)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0): dependencies: esbuild: 0.24.2 diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 151c48fc1..a2d0c16d7 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -6,6 +6,7 @@ import rehypeSlug from 'rehype-slug'; import rehypeAutolinkHeadings from 'rehype-autolink-headings'; import rehypeUrls from 'rehype-urls'; import bundleAudioWorkletPlugin from 'vite-plugin-bundle-audioworklet'; +import wasm from 'vite-plugin-wasm'; import tailwind from '@astrojs/tailwind'; import AstroPWA from '@vite-pwa/astro'; @@ -135,7 +136,10 @@ export default defineConfig({ site, base, vite: { - plugins: [bundleAudioWorkletPlugin()], + optimizeDeps: { + exclude: ["brotli-wasm", "brotli-wasm/pkg.bundler/brotli_wasm_bg.wasm"], + }, + plugins: [bundleAudioWorkletPlugin(),wasm()], ssr: { // Example: Force a broken package to skip SSR processing, if needed // external: ['fraction.js'], // https://github.com/infusion/Fraction.js/issues/51 diff --git a/website/package.json b/website/package.json index 499758147..9597af9bb 100644 --- a/website/package.json +++ b/website/package.json @@ -54,6 +54,8 @@ "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "astro": "^5.1.9", + "base64url-universal": "^2.0.0", + "brotli-wasm": "^3.0.1", "claviature": "^0.1.0", "date-fns": "^4.1.0", "hs2js": "0.1.0", @@ -68,6 +70,7 @@ "rehype-urls": "^1.2.0", "remark-toc": "^9.0.0", "tailwindcss": "^3.4.17", + "vite-plugin-wasm": "^3.4.1", "worker-timers": "^8.0.13" }, "devDependencies": { diff --git a/website/src/repl/useReplContext.jsx b/website/src/repl/useReplContext.jsx index 12621c50a..0f6a56eee 100644 --- a/website/src/repl/useReplContext.jsx +++ b/website/src/repl/useReplContext.jsx @@ -35,6 +35,10 @@ import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs'; import './Repl.css'; import { setInterval, clearInterval } from 'worker-timers'; import { getMetadata } from '../metadata_parser'; +import brotliPromise from 'brotli-wasm'; +import { encode as base64urlencode } from 'base64url-universal'; + +const brotli = await brotliPromise; // Import is async in browsers due to wasm requirements! const { latestCode, maxPolyphony, audioDeviceName, multiChannelOrbits } = settingsMap.get(); let modulesLoading, presets, drawContext, clearCanvas, audioReady; @@ -99,7 +103,17 @@ export function useReplContext() { window.parent?.postMessage(code); setLatestCode(code); - window.location.hash = '#' + code2hash(code); + //window.location.hash = '#' + code2hash(code); + + // Compress the script with brotli and encode it with base64url + const encoded = new TextEncoder().encode(code); + //console.log('encoded',encoded); + const brotlized = brotli.compress(encoded, { mode: 1, quality: 11 }); + //console.log('brotlized',brotlized); + const baseurled = base64urlencode(brotlized); + //console.log('baseurled',baseurled); + window.location.hash = '#~' + baseurled; + setDocumentTitle(code); const viewingPatternData = getViewingPatternData(); setVersionDefaultsFrom(code); diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs index ac27158f4..a3106a0e5 100644 --- a/website/src/repl/util.mjs +++ b/website/src/repl/util.mjs @@ -8,6 +8,10 @@ import { createClient } from '@supabase/supabase-js'; import { nanoid } from 'nanoid'; import { writeText } from '@tauri-apps/plugin-clipboard-manager'; import { $featuredPatterns /* , loadDBPatterns */ } from '@src/user_pattern_utils.mjs'; +import brotliPromise from 'brotli-wasm'; +import { decode as base64urldecode } from 'base64url-universal'; + +const brotli = await brotliPromise; // Import is async in browsers due to wasm requirements! // Create a single supabase client for interacting with your database export const supabase = createClient( @@ -27,6 +31,19 @@ export async function initCode() { const hash = initialUrl.split('?')[1]?.split('#')?.[0]?.split('&')[0]; const codeParam = window.location.href.split('#')[1] || ''; if (codeParam) { + if (codeParam[0] === '~') + { + // Encoded using base64url and Compressed by brotli + const baseurled = codeParam.substring(1); + //console.log('baseurled', baseurled); + const brotlized = base64urldecode(baseurled); + //console.log('brotlized', brotlized); + const encoded = brotli.decompress(brotlized); + //console.log('encoded', encoded); + const decoded = new TextDecoder().decode(encoded); + //console.log('decoded', decoded); + return decoded; + } // looking like https://strudel.cc/#ImMzIGUzIg%3D%3D (hash length depends on code length) return hash2code(codeParam); } else if (hash) {