From b54733d8b7725e1fdeda880e4ce6ef9324802aaa Mon Sep 17 00:00:00 2001 From: Ivan Jukic Date: Wed, 5 Feb 2025 18:09:43 +0100 Subject: [PATCH] Use Vite for dev builds - now with HMR! --- This PR introduces Vite as our solution for running dev builds. It provides an "out-of-the-box" hot module replacing, which should help us build those new UI features much quicker. There is quite a few changes in this PR, but the gist is that they're quite relevant for making us faster. There is also a number of "fixes", that align the codebase with the latest syntax, and some improvements like the ability to alias imports, which might be easier to reconcile than relative paths. --- .gitignore | 3 + frontend/app/build_dev.sh | 38 +- frontend/app/build_dev_workers.sh | 24 + frontend/app/build_prod.sh | 27 +- frontend/app/build_prod_test.sh | 27 +- frontend/app/build_testnet.sh | 22 +- frontend/app/index.html | 59 + frontend/app/package.json | 12 +- frontend/app/rollup.config.mjs | 304 +-- frontend/app/rollup.extras.mjs | 155 ++ frontend/app/src/components/App.svelte | 369 +-- frontend/app/src/components/Router.svelte | 2 +- .../app/src/components/UpgradeBanner.svelte | 4 +- frontend/app/src/components/bots/botState.ts | 2 +- .../components/home/ChatMessageMenu.svelte | 2 +- .../src/components/home/GenericPreview.svelte | 2 +- .../src/components/home/GiphySelector.svelte | 8 +- .../src/components/home/LinkPreviews.svelte | 4 - .../app/src/components/home/Markdown.svelte | 144 -- .../src/components/home/SigninWithEth.svelte | 2 +- .../src/components/home/SigninWithSol.svelte | 2 +- frontend/app/src/components/home/Tweet.svelte | 6 +- .../home/access/DiamondGateEvaluator.svelte | 4 +- .../admin/ReviewTranslationCorrections.svelte | 4 +- .../home/profile/LinkedAuthAccounts.svelte | 10 +- .../home/profile/UserProfile.svelte | 4 +- .../home/profile/ViewUserProfile.svelte | 8 +- .../home/proposal/MakeProposalModal.svelte | 8 +- .../proposals/ProposalVotingProgress.svelte | 10 +- .../components/home/upgrade/Upgrade.svelte | 4 +- .../components/landingpages/HomePage.svelte | 2 + frontend/app/src/styles/global.scss | 143 +- frontend/app/src/styles/mixins.scss | 35 +- frontend/app/src/utils/devices.ts | 4 +- frontend/app/src/utils/notifications.ts | 4 +- frontend/app/src/utils/trace.ts | 2 +- frontend/app/src/utils/urls.ts | 2 +- frontend/app/svelte.config.js | 27 +- frontend/app/tsconfig.json | 18 +- frontend/app/turbo.json | 10 +- frontend/app/vite.config.ts | 66 + frontend/openchat-client/src/openchat.ts | 3 +- .../openchat-client/src/stores/permission.ts | 2 +- frontend/openchat-client/tsconfig.json | 2 +- frontend/openchat-shared/src/constants.ts | 2 +- frontend/openchat-shared/tsconfig.json | 2 +- frontend/package-lock.json | 2205 ++++++++++++++++- frontend/tsconfig.json | 17 +- frontend/vite-env.d.ts | 41 + 49 files changed, 3004 insertions(+), 853 deletions(-) create mode 100755 frontend/app/build_dev_workers.sh create mode 100644 frontend/app/index.html create mode 100644 frontend/app/rollup.extras.mjs create mode 100644 frontend/app/vite.config.ts create mode 100644 frontend/vite-env.d.ts diff --git a/.gitignore b/.gitignore index 13d6af8895..be8efaa57b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ temp.did website_release.md frontend/app/src/stores/timeline.ts /Makefile + +**/public/worker.js +**/public/service_worker.js diff --git a/frontend/app/build_dev.sh b/frontend/app/build_dev.sh index cdd2c50a50..cf4815fb70 100755 --- a/frontend/app/build_dev.sh +++ b/frontend/app/build_dev.sh @@ -1,17 +1,27 @@ -WATCH=$1 -export BUILD_ENV=development -export INTERNET_IDENTITY_CANISTER_ID=qhbym-qaaaa-aaaaa-aaafq-cai -export INTERNET_IDENTITY_URL=http://127.0.0.1:8080?canisterId=qhbym-qaaaa-aaaaa-aaafq-cai -export NFID_URL=http://localhost:8080?canisterId=qhbym-qaaaa-aaaaa-aaafq-cai -export VIDEO_BRIDGE_URL=http://localhost:5050 -export PREVIEW_PROXY_URL=https://dy7sqxe9if6te.cloudfront.net - -export DFX_NETWORK=local -export DEV_PORT=5001 -export BLOB_URL_PATTERN=http://{canisterId}.localhost:8080/{blobType} -export ACHIEVEMENT_URL_PATH=http://{canisterId}.localhost:8080 -export WALLET_CONNECT_PROJECT_ID=b9aafebed2abfaf8341afd9428c947d5 +# Non OC env variables export NODE_OPTIONS="--max-old-space-size=8192" +export NODE_ENV=development + +# OC specific env variables +export OC_ACHIEVEMENT_URL_PATH=http://{canisterId}.localhost:8080 +export OC_BLOB_URL_PATTERN=http://{canisterId}.localhost:8080/{blobType} +export OC_BUILD_ENV=$NODE_ENV +export OC_DEV_PORT=5001 +export OC_DFX_NETWORK=local +export OC_INTERNET_IDENTITY_CANISTER_ID=qhbym-qaaaa-aaaaa-aaafq-cai +export OC_INTERNET_IDENTITY_URL=http://127.0.0.1:8080?canisterId=qhbym-qaaaa-aaaaa-aaafq-cai +export OC_NFID_URL=http://localhost:8080?canisterId=qhbym-qaaaa-aaaaa-aaafq-cai +export OC_NODE_ENV=$NODE_ENV +export OC_PREVIEW_PROXY_URL=https://dy7sqxe9if6te.cloudfront.net +export OC_VIDEO_BRIDGE_URL=http://localhost:5050 +export OC_WALLET_CONNECT_PROJECT_ID=b9aafebed2abfaf8341afd9428c947d5 +export OC_WEBSITE_VERSION= + +# Check if worker files are available, build them if not +if [ ! -f ./public/worker.js ] || [ ! -f ./public/service_worker.js ]; then + npm run dev:workers || exit 1 +fi -npx rollup -c $WATCH +# Run dev app +npx vite --strictPort --port $OC_DEV_PORT diff --git a/frontend/app/build_dev_workers.sh b/frontend/app/build_dev_workers.sh new file mode 100755 index 0000000000..2d8df8da7d --- /dev/null +++ b/frontend/app/build_dev_workers.sh @@ -0,0 +1,24 @@ +RED='\033[0;31m' +NC='\033[0m' + +# Build worker files and copy the required ones to public for Vite to be able +# to serve them! (Not the nicest solution, but should work; or we get the Vite +# custom server to work.) +npm --prefix ../openchat-worker run build || exit 1 +npm --prefix ../openchat-service-worker run build || exit 1 + +files=( \ + "../openchat-worker/lib/worker.js" \ + "../openchat-service-worker/lib/service_worker.js" \ +) + +for file in ${files[@]}; +do + echo "Copying file ${file}..." + if [ -f $file ]; then + cp $file ./public + else + echo -e "${RED}ERROR: ${file} does not exist!${NC}\n" + exit 1 + fi; +done diff --git a/frontend/app/build_prod.sh b/frontend/app/build_prod.sh index 30f77e3fb0..19fed84618 100755 --- a/frontend/app/build_prod.sh +++ b/frontend/app/build_prod.sh @@ -1,15 +1,16 @@ -export BUILD_ENV=production -export INTERNET_IDENTITY_CANISTER_ID=rdmx6-jaaaa-aaaaa-aaadq-cai -export INTERNET_IDENTITY_URL=https://identity.ic0.app -export NFID_URL=https://nfid.one/authenticate/?applicationName=OpenChat -export DFX_NETWORK=ic -export VIDEO_BRIDGE_URL=https://d7ufu5rwdb6eb.cloudfront.net -export IC_URL=https://icp-api.io -export II_DERIVATION_ORIGIN=https://6hsbt-vqaaa-aaaaf-aaafq-cai.ic0.app -export CUSTOM_DOMAINS=oc.app,webtest.oc.app -export BLOB_URL_PATTERN=https://{canisterId}.raw.icp0.io/{blobType} -export ACHIEVEMENT_URL_PATH=https://{canisterId}.raw.icp0.io -export WALLET_CONNECT_PROJECT_ID=adf8b4a7c5514a8229981aabdee2e246 -export PREVIEW_PROXY_URL=https://dy7sqxe9if6te.cloudfront.net +export OC_BUILD_ENV=production +export OC_NODE_ENV=$NODE_ENV +export OC_INTERNET_IDENTITY_CANISTER_ID=rdmx6-jaaaa-aaaaa-aaadq-cai +export OC_INTERNET_IDENTITY_URL=https://identity.ic0.app +export OC_NFID_URL=https://nfid.one/authenticate/?applicationName=OpenChat +export OC_DFX_NETWORK=ic +export OC_VIDEO_BRIDGE_URL=https://d7ufu5rwdb6eb.cloudfront.net +export OC_IC_URL=https://icp-api.io +export OC_II_DERIVATION_ORIGIN=https://6hsbt-vqaaa-aaaaf-aaafq-cai.ic0.app +export OC_CUSTOM_DOMAINS=oc.app,webtest.oc.app +export OC_BLOB_URL_PATTERN=https://{canisterId}.raw.icp0.io/{blobType} +export OC_ACHIEVEMENT_URL_PATH=https://{canisterId}.raw.icp0.io +export OC_WALLET_CONNECT_PROJECT_ID=adf8b4a7c5514a8229981aabdee2e246 +export OC_PREVIEW_PROXY_URL=https://dy7sqxe9if6te.cloudfront.net npx rollup -c diff --git a/frontend/app/build_prod_test.sh b/frontend/app/build_prod_test.sh index 3cacec08b4..0ecfe95bd8 100755 --- a/frontend/app/build_prod_test.sh +++ b/frontend/app/build_prod_test.sh @@ -1,15 +1,16 @@ -export BUILD_ENV=prod_test -export INTERNET_IDENTITY_CANISTER_ID=rdmx6-jaaaa-aaaaa-aaadq-cai -export INTERNET_IDENTITY_URL=https://identity.ic0.app -export NFID_URL=https://nfid.one/authenticate/?applicationName=OpenChatTest -export VIDEO_BRIDGE_URL=https://d37cwaycp9g5li.cloudfront.net -export DFX_NETWORK=ic_test -export IC_URL=https://icp-api.io -export II_DERIVATION_ORIGIN=https://pfs7b-iqaaa-aaaaf-abs7q-cai.ic0.app -export CUSTOM_DOMAINS=test.oc.app -export BLOB_URL_PATTERN=https://{canisterId}.raw.icp0.io/{blobType} -export ACHIEVEMENT_URL_PATH=https://{canisterId}.raw.icp0.io -export WALLET_CONNECT_PROJECT_ID=b9aafebed2abfaf8341afd9428c947d5 -export PREVIEW_PROXY_URL=https://dy7sqxe9if6te.cloudfront.net +export OC_BUILD_ENV=prod_test +export OC_NODE_ENV=$NODE_ENV +export OC_INTERNET_IDENTITY_CANISTER_ID=rdmx6-jaaaa-aaaaa-aaadq-cai +export OC_INTERNET_IDENTITY_URL=https://identity.ic0.app +export OC_NFID_URL=https://nfid.one/authenticate/?applicationName=OpenChatTest +export OC_VIDEO_BRIDGE_URL=https://d37cwaycp9g5li.cloudfront.net +export OC_DFX_NETWORK=ic_test +export OC_IC_URL=https://icp-api.io +export OC_II_DERIVATION_ORIGIN=https://pfs7b-iqaaa-aaaaf-abs7q-cai.ic0.app +export OC_CUSTOM_DOMAINS=test.oc.app +export OC_BLOB_URL_PATTERN=https://{canisterId}.raw.icp0.io/{blobType} +export OC_ACHIEVEMENT_URL_PATH=https://{canisterId}.raw.icp0.io +export OC_WALLET_CONNECT_PROJECT_ID=b9aafebed2abfaf8341afd9428c947d5 +export OC_PREVIEW_PROXY_URL=https://dy7sqxe9if6te.cloudfront.net npx rollup -c diff --git a/frontend/app/build_testnet.sh b/frontend/app/build_testnet.sh index b76e971018..736048416f 100755 --- a/frontend/app/build_testnet.sh +++ b/frontend/app/build_testnet.sh @@ -4,15 +4,17 @@ then exit 1 fi -export BUILD_ENV=testnet -export INTERNET_IDENTITY_CANISTER_ID=rdmx6-jaaaa-aaaaa-aaadq-cai -export INTERNET_IDENTITY_URL=https://qhbym-qaaaa-aaaaa-aaafq-cai.$DFX_NETWORK.testnet.dfinity.network/ -export NFID_URL=https://qhbym-qaaaa-aaaaa-aaafq-cai.$DFX_NETWORK.testnet.dfinity.network/ -export IC_URL=https://$DFX_NETWORK.testnet.dfinity.network/ -export BLOB_URL_PATTERN=https://{canisterId}.raw.$DFX_NETWORK.testnet.dfinity.network/{blobType} -export ACHIEVEMENT_URL_PATH=https://{canisterId}.raw.icp0.io -export VIDEO_BRIDGE_URL=https://d37cwaycp9g5li.cloudfront.net -export WALLET_CONNECT_PROJECT_ID=b9aafebed2abfaf8341afd9428c947d5 -export PREVIEW_PROXY_URL=https://dy7sqxe9if6te.cloudfront.net +export OC_DFX_NETWORK=$DFX_NETWORK +export OC_BUILD_ENV=testnet +export OC_NODE_ENV=$NODE_ENV +export OC_INTERNET_IDENTITY_CANISTER_ID=rdmx6-jaaaa-aaaaa-aaadq-cai +export OC_INTERNET_IDENTITY_URL=https://qhbym-qaaaa-aaaaa-aaafq-cai.$DFX_NETWORK.testnet.dfinity.network/ +export OC_NFID_URL=https://qhbym-qaaaa-aaaaa-aaafq-cai.$DFX_NETWORK.testnet.dfinity.network/ +export OC_IC_URL=https://$DFX_NETWORK.testnet.dfinity.network/ +export OC_BLOB_URL_PATTERN=https://{canisterId}.raw.$DFX_NETWORK.testnet.dfinity.network/{blobType} +export OC_ACHIEVEMENT_URL_PATH=https://{canisterId}.raw.icp0.io +export OC_VIDEO_BRIDGE_URL=https://d37cwaycp9g5li.cloudfront.net +export OC_WALLET_CONNECT_PROJECT_ID=b9aafebed2abfaf8341afd9428c947d5 +export OC_PREVIEW_PROXY_URL=https://dy7sqxe9if6te.cloudfront.net npx rollup -c diff --git a/frontend/app/index.html b/frontend/app/index.html new file mode 100644 index 0000000000..b31070f585 --- /dev/null +++ b/frontend/app/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OpenChat + + + + + + + + + <%- csp %> + <%- injectScript %> + + + + + + + + diff --git a/frontend/app/package.json b/frontend/app/package.json index c0d0cbe883..e5c065e668 100644 --- a/frontend/app/package.json +++ b/frontend/app/package.json @@ -2,12 +2,14 @@ "name": "openchat_app", "version": "2.0.0", "private": true, + "type": "module", "scripts": { - "build": "sh ./build_dev.sh", + "build": "sh ./build_prod.sh", "build:prod": "sh ./build_prod.sh", "build:prod_test": "sh ./build_prod_test.sh", "build:testnet": "sh ./build_testnet.sh", - "dev": "sh ./build_dev.sh -w", + "dev": "sh ./build_dev.sh", + "dev:workers": "sh ./build_dev_workers.sh", "start": "sirv public --no-clear", "validate": "svelte-check --threshold error", "lint": "eslint ./src --fix", @@ -19,6 +21,7 @@ }, "devDependencies": { "@google-cloud/translate": "^8.0.1", + "@sveltejs/vite-plugin-svelte": "^5.0.3", "@tsconfig/svelte": "^5.0.0", "@types/dom-mediacapture-record": "^1.0.16", "@types/dompurify": "^3.0.2", @@ -50,9 +53,12 @@ "stream-browserify": "3.0.0", "svelte-preprocess": "^6.0.3", "tslib": "^2.6.1", + "tsx": "^4.19.2", "typescript": "=5.4.2", "url": "^0.11.1", - "util": "0.12.5" + "util": "0.12.5", + "vite": "^6.0.11", + "vite-plugin-html": "^3.2.2" }, "dependencies": { "@daily-co/daily-js": "^0.72.0", diff --git a/frontend/app/rollup.config.mjs b/frontend/app/rollup.config.mjs index 5dab6f6a85..4d033aa238 100644 --- a/frontend/app/rollup.config.mjs +++ b/frontend/app/rollup.config.mjs @@ -5,139 +5,28 @@ import commonjs from "@rollup/plugin-commonjs"; import html from "@rollup/plugin-html"; import resolve from "@rollup/plugin-node-resolve"; import copy from "rollup-plugin-copy"; -import livereload from "rollup-plugin-livereload"; import terser from "@rollup/plugin-terser"; import typescript from "@rollup/plugin-typescript"; -import dfxJson from "../../dfx.json" assert { type: "json" }; import inject from "rollup-plugin-inject"; -import dev from "rollup-plugin-dev"; import json from "@rollup/plugin-json"; import analyze from "rollup-plugin-analyzer"; import filesize from "rollup-plugin-filesize"; import styles from "rollup-styles"; import autoprefixer from "autoprefixer"; -import { sha256 } from "js-sha256"; -import dotenv from "dotenv"; import replace from "@rollup/plugin-replace"; import fs from "fs-extra"; import path from "path"; import rimraf from "rimraf"; -import { fileURLToPath } from "url"; import { sourcemapNewline } from "../sourcemapNewline.mjs"; +import { + initEnv, + manualChunks, + copyFile, + generateCspForScripts, + maybeStringify, + __dirname +} from "./rollup.extras.mjs"; -const dirname = path.dirname(fileURLToPath(import.meta.url)); - -dotenv.config({ path: path.join(dirname, "../.env") }); - -const dfxNetwork = process.env.DFX_NETWORK; - -console.log("DFX_NETWORK: ", dfxNetwork); - -if (dfxNetwork) { - const dfxJsonPath = path.join(dirname, "../..", "dfx.json"); - const dfxJson = JSON.parse(fs.readFileSync(dfxJsonPath)); - const canisterPath = - dfxJson["networks"][dfxNetwork]["type"] === "persistent" - ? path.join(dirname, "../..", "canister_ids.json") - : path.join(dirname, "../..", ".dfx", dfxNetwork, "canister_ids.json"); - - if (fs.existsSync(canisterPath)) { - const canisters = JSON.parse(fs.readFileSync(canisterPath)); - process.env.TRANSLATIONS_CANISTER = canisters.translations[dfxNetwork]; - process.env.USER_INDEX_CANISTER = canisters.user_index[dfxNetwork]; - process.env.GROUP_INDEX_CANISTER = canisters.group_index[dfxNetwork]; - process.env.NOTIFICATIONS_CANISTER = canisters.notifications_index[dfxNetwork]; - process.env.IDENTITY_CANISTER = canisters.identity[dfxNetwork]; - process.env.ONLINE_CANISTER = canisters.online_users[dfxNetwork]; - process.env.PROPOSALS_BOT_CANISTER = canisters.proposals_bot[dfxNetwork]; - process.env.AIRDROP_BOT_CANISTER = canisters.airdrop_bot[dfxNetwork]; - process.env.STORAGE_INDEX_CANISTER = canisters.storage_index[dfxNetwork]; - process.env.REGISTRY_CANISTER = canisters.registry[dfxNetwork]; - process.env.MARKET_MAKER_CANISTER = canisters.market_maker[dfxNetwork]; - process.env.SIGN_IN_WITH_EMAIL_CANISTER = canisters.sign_in_with_email[dfxNetwork]; - process.env.SIGN_IN_WITH_ETHEREUM_CANISTER = canisters.sign_in_with_ethereum[dfxNetwork]; - process.env.SIGN_IN_WITH_SOLANA_CANISTER = canisters.sign_in_with_solana[dfxNetwork]; - - console.log("TranslationsCanisterId: ", process.env.TRANSLATIONS_CANISTER); - console.log("UserIndexCanisterId: ", process.env.USER_INDEX_CANISTER); - console.log("GroupIndexCanisterId: ", process.env.GROUP_INDEX_CANISTER); - console.log("NotificationsCanisterId: ", process.env.NOTIFICATIONS_CANISTER); - console.log("IdentityCanisterId: ", process.env.IDENTITY_CANISTER); - console.log("OnlineCanisterId: ", process.env.ONLINE_CANISTER); - console.log("ProposalsBotCanisterId: ", process.env.PROPOSALS_BOT_CANISTER); - console.log("AirdropBotCanisterId: ", process.env.AIRDROP_BOT_CANISTER); - console.log("StorageIndex: ", process.env.STORAGE_INDEX_CANISTER); - console.log("Registry: ", process.env.REGISTRY_CANISTER); - console.log("MarketMaker: ", process.env.MARKET_MAKER_CANISTER); - console.log("SignInWithEmail: ", process.env.SIGN_IN_WITH_EMAIL_CANISTER); - console.log("SignInWithEthereum: ", process.env.SIGN_IN_WITH_ETHEREUM_CANISTER); - console.log("SignInWithSolana: ", process.env.SIGN_IN_WITH_SOLANA_CANISTER); - } else { - console.log( - "Couldn't find canisters JSON at: ", - canisterPath, - ". Falling back to original env vars.", - ); - } -} else { - console.log( - "DFX_NETWORK env var not set, cannot load correct canisterIds, falling back to original env vars.", - ); -} - -const build_env = process.env.BUILD_ENV; -const production = build_env === "production"; -const development = build_env === "development"; -const testnet = !development && !production; -const watch = process.env.ROLLUP_WATCH; - -const env = process.env.NODE_ENV ?? (development ? "development" : "production"); -const version = process.env.OPENCHAT_WEBSITE_VERSION; -if (!development && !version) { - throw Error("OPENCHAT_WEBSITE_VERSION environment variable not set"); -} -if (production && !process.env.ROLLBAR_ACCESS_TOKEN) { - throw Error("ROLLBAR_ACCESS_TOKEN environment variable not set"); -} -if (production && !process.env.USERGEEK_APIKEY) { - throw Error("USERGEEK_APIKEY environment variable not set"); -} -if (production && !process.env.METERED_APIKEY) { - throw Error("METERED_APIKEY environment variable not set"); -} -const SERVICE_WORKER_PATH = `/service_worker.js?v=${version}`; - -console.log("BUILD_ENV", build_env); -console.log("ENV", env); -console.log("INTERNET IDENTITY URL", process.env.INTERNET_IDENTITY_URL); -console.log("INTERNET IDENTITY CANISTER", process.env.INTERNET_IDENTITY_CANISTER_ID); -console.log("NFID URL", process.env.NFID_URL); -console.log("VERSION", version ?? "undefined"); - -function serve() { - return dev({ - dirs: ["./build", "./public"], - proxy: [ - { - from: "/api/*", - to: `http://${dfxJson.networks.local.bind}`, - }, - ], - spa: "./index.html", - port: process.env.DEV_PORT || 5000, - }); -} - -function copyFile(fromPath, toPath, file) { - const from = path.join(dirname, fromPath, file); - const to = path.join(dirname, toPath, file); - if (fs.existsSync(from)) { - console.log("Copying file -> : ", from, to); - fs.copySync(from, to, { - recursive: true, - }); - } -} // this is a bit ridiculous but there we are ... function clean() { @@ -145,12 +34,12 @@ function clean() { name: "clean-build", renderStart() { console.log("cleaning up the build directory"); - rimraf.sync(path.join(dirname, "build")); + rimraf.sync(path.join(__dirname, "build")); fs.mkdirSync("build"); if (version) { fs.writeFileSync("build/version", JSON.stringify({ version })); } - const customDomains = process.env.CUSTOM_DOMAINS; + const customDomains = process.env.OC_CUSTOM_DOMAINS; if (customDomains !== undefined) { fs.mkdirSync("build/.well-known"); fs.writeFileSync( @@ -169,28 +58,8 @@ function clean() { }; } -// Put external dependencies into their own bundle so that they get cached separately -function manualChunks(id) { - if (id.includes("node_modules")) { - return "vendor"; - } -} -function transformSourceMappingUrl(contents) { - return contents.toString().replace("//# sourceMappingURL=", "//# sourceMappingURL=./_/raw/"); -} - -function watchExternalFiles() { - return { - name: "watch-external-files", - buildStart() { - this.addWatchFile( - path.resolve(dirname, "../openchat-service-worker/lib/service_worker.js"), - ); - this.addWatchFile(path.resolve(dirname, "../openchat-worker/lib/worker.js")); - }, - }; -} +const { version } = initEnv(); export default { input: `./src/main.ts`, @@ -208,11 +77,10 @@ export default { preprocess: sveltePreprocess({ sourceMap: true, scss: { - prependData: `@use 'sass:math'; @import 'src/styles/mixins.scss';`, + prependData: `@use 'sass:math'; @use 'src/styles/mixins.scss' as *;`, }, }), compilerOptions: { - dev: development, // immutable: true, // this could be a great optimisation, but we need to plan for it a bit }, onwarn: (warning, handler) => { @@ -229,7 +97,9 @@ export default { dedupe: ["svelte"], }), commonjs(), - typescript(), + typescript({ + include: ["./src/**/*", "../vite-env.d.ts"], + }), inject({ Buffer: ["buffer", "Buffer"], process: "process/browser", @@ -238,91 +108,72 @@ export default { replace({ preventAssignment: true, - "process.env.INTERNET_IDENTITY_URL": JSON.stringify(process.env.INTERNET_IDENTITY_URL), - "process.env.INTERNET_IDENTITY_CANISTER_ID": JSON.stringify( - process.env.INTERNET_IDENTITY_CANISTER_ID, + "import.meta.env.OC_INTERNET_IDENTITY_URL": JSON.stringify(process.env.OC_INTERNET_IDENTITY_URL), + "import.meta.env.OC_INTERNET_IDENTITY_CANISTER_ID": JSON.stringify( + process.env.OC_INTERNET_IDENTITY_CANISTER_ID, ), - "process.env.NFID_URL": JSON.stringify(process.env.NFID_URL), - "process.env.DFX_NETWORK": JSON.stringify(dfxNetwork), - "process.env.NODE_ENV": JSON.stringify(env), - "process.env.OPENCHAT_WEBSITE_VERSION": JSON.stringify(version), - "process.env.ROLLBAR_ACCESS_TOKEN": JSON.stringify(process.env.ROLLBAR_ACCESS_TOKEN), - "process.env.IC_URL": maybeStringify(process.env.IC_URL), - "process.env.II_DERIVATION_ORIGIN": maybeStringify(process.env.II_DERIVATION_ORIGIN), - "process.env.USER_INDEX_CANISTER": JSON.stringify(process.env.USER_INDEX_CANISTER), - "process.env.TRANSLATIONS_CANISTER": JSON.stringify(process.env.TRANSLATIONS_CANISTER), - "process.env.GROUP_INDEX_CANISTER": JSON.stringify(process.env.GROUP_INDEX_CANISTER), - "process.env.NOTIFICATIONS_CANISTER": JSON.stringify( - process.env.NOTIFICATIONS_CANISTER, + "import.meta.env.OC_NFID_URL": JSON.stringify(process.env.OC_NFID_URL), + "import.meta.env.OC_DFX_NETWORK": JSON.stringify(process.env.OC_DFX_NETWORK), + "import.meta.env.OC_NODE_ENV": JSON.stringify(process.env.NODE_ENV ?? "production"), + "import.meta.env.OC_OC_WEBSITE_VERSION": JSON.stringify(process.env.OC_WEBSITE_VERSION), + "import.meta.env.OC_ROLLBAR_ACCESS_TOKEN": JSON.stringify(process.env.OC_ROLLBAR_ACCESS_TOKEN), + "import.meta.env.OC_IC_URL": maybeStringify(process.env.OC_IC_URL), + "import.meta.env.OC_II_DERIVATION_ORIGIN": maybeStringify(process.env.OC_II_DERIVATION_ORIGIN), + "import.meta.env.OC_USER_INDEX_CANISTER": JSON.stringify(process.env.OC_USER_INDEX_CANISTER), + "import.meta.env.OC_TRANSLATIONS_CANISTER": JSON.stringify(process.env.OC_TRANSLATIONS_CANISTER), + "import.meta.env.OC_GROUP_INDEX_CANISTER": JSON.stringify(process.env.OC_GROUP_INDEX_CANISTER), + "import.meta.env.OC_NOTIFICATIONS_CANISTER": JSON.stringify( + process.env.OC_NOTIFICATIONS_CANISTER, ), - "process.env.IDENTITY_CANISTER": JSON.stringify(process.env.IDENTITY_CANISTER), - "process.env.ONLINE_CANISTER": JSON.stringify(process.env.ONLINE_CANISTER), - "process.env.PROPOSALS_BOT_CANISTER": JSON.stringify( - process.env.PROPOSALS_BOT_CANISTER, + "import.meta.env.OC_IDENTITY_CANISTER": JSON.stringify(process.env.OC_IDENTITY_CANISTER), + "import.meta.env.OC_ONLINE_CANISTER": JSON.stringify(process.env.OC_ONLINE_CANISTER), + "import.meta.env.OC_PROPOSALS_BOT_CANpath._CANISTER": JSON.stringify( + process.env.OC_PROPOSALS_BOT_CANISTER, ), - "process.env.AIRDROP_BOT_CANISTER": JSON.stringify(process.env.AIRDROP_BOT_CANISTER), - "process.env.STORAGE_INDEX_CANISTER": JSON.stringify( - process.env.STORAGE_INDEX_CANISTER, + "import.meta.env.OC_AIRDROP_BOT_CANISTER": JSON.stringify(process.env.OC_AIRDROP_BOT_CANISTER), + "import.meta.env.OC_STORAGE_INDEX_CANISTER": JSON.stringify( + process.env.OC_STORAGE_INDEX_CANISTER, ), - "process.env.REGISTRY_CANISTER": JSON.stringify(process.env.REGISTRY_CANISTER), - "process.env.MARKET_MAKER_CANISTER": JSON.stringify(process.env.MARKET_MAKER_CANISTER), - "process.env.SIGN_IN_WITH_EMAIL_CANISTER": JSON.stringify( - process.env.SIGN_IN_WITH_EMAIL_CANISTER, + "import.meta.env.OC_REGISTRY_CANISTER": JSON.stringify(process.env.OC_REGISTRY_CANISTER), + "import.meta.env.OC_MARKET_MAKER_CANISTER": JSON.stringify(process.env.OC_MARKET_MAKER_CANISTER), + "import.meta.env.OC_SIGN_IN_WITH_EMAIL_CANISTER": JSON.stringify( + process.env.OC_SIGN_IN_WITH_EMAIL_CANISTER, ), - "process.env.SIGN_IN_WITH_ETHEREUM_CANISTER": JSON.stringify( - process.env.SIGN_IN_WITH_ETHEREUM_CANISTER, + "import.meta.env.OC_SIGN_IN_WITH_ETHEREUM_CANISTER": JSON.stringify( + process.env.OC_SIGN_IN_WITH_ETHEREUM_CANISTER, ), - "process.env.SIGN_IN_WITH_SOLANA_CANISTER": JSON.stringify( - process.env.SIGN_IN_WITH_SOLANA_CANISTER, + "import.meta.env.OC_SIGN_IN_WITH_SOLANA_CANISTER": JSON.stringify( + process.env.OC_SIGN_IN_WITH_SOLANA_CANISTER, ), - "process.env.BLOB_URL_PATTERN": JSON.stringify(process.env.BLOB_URL_PATTERN), - "process.env.ACHIEVEMENT_URL_PATH": JSON.stringify(process.env.ACHIEVEMENT_URL_PATH), - "process.env.USERGEEK_APIKEY": JSON.stringify(process.env.USERGEEK_APIKEY), - "process.env.VIDEO_BRIDGE_URL": JSON.stringify(process.env.VIDEO_BRIDGE_URL), - "process.env.PREVIEW_PROXY_URL": JSON.stringify(process.env.PREVIEW_PROXY_URL), - "process.env.METERED_APIKEY": JSON.stringify(process.env.METERED_APIKEY), - "process.env.TENOR_APIKEY": JSON.stringify(process.env.TENOR_APIKEY), - "process.env.CORS_APIKEY": JSON.stringify(process.env.CORS_APIKEY), - "process.env.PUBLIC_TRANSLATE_API_KEY": JSON.stringify( - process.env.PUBLIC_TRANSLATE_API_KEY, + "import.meta.env.OC_BLOB_URL_PATTERN": JSON.stringify(process.env.OC_BLOB_URL_PATTERN), + "import.meta.env.OC_ACHIEVEMENT_URL_PATH": JSON.stringify(process.env.OC_ACHIEVEMENT_URL_PATH), + "import.meta.env.OC_USERGEEK_APIKEY": JSON.stringify(process.env.OC_USERGEEK_APIKEY), + "import.meta.env.OC_VIDEO_BRIDGE_URL": JSON.stringify(process.env.OC_VIDEO_BRIDGE_URL), + "import.meta.env.OC_PREVIEW_PROXY_URL": JSON.stringify(process.env.OC_PREVIEW_PROXY_URL), + "import.meta.env.OC_METERED_APIKEY": JSON.stringify(process.env.OC_METERED_APIKEY), + "import.meta.env.OC_TENOR_APIKEY": JSON.stringify(process.env.OC_TENOR_APIKEY), + "import.meta.env.OC_CORS_APIKEY": JSON.stringify(process.env.OC_CORS_APIKEY), + "import.meta.env.OC_PUBLIC_TRANSLATE_API_KEY": JSON.stringify( + process.env.OC_PUBLIC_TRANSLATE_API_KEY, ), - "process.env.WALLET_CONNECT_PROJECT_ID": JSON.stringify( - process.env.WALLET_CONNECT_PROJECT_ID, + "import.meta.env.OC_WALLET_CONNECT_PROJECT_ID": JSON.stringify( + process.env.OC_WALLET_CONNECT_PROJECT_ID, ), - "process.env.SERVICE_WORKER_PATH": SERVICE_WORKER_PATH, - "process.env.SUSPICIOUS_USERIDS": process.env.SUSPICIOUS_USERIDS, + "import.meta.env.OC_SERVICE_WORKER_PATH": process.env.OC_SERVICE_WORKER_PATH, + "import.meta.env.OC_SUSPICIOUS_USERIDS": process.env.OC_SUSPICIOUS_USERIDS, }), html({ template: ({ files }) => { const jsEntryFile = files.js.find((f) => f.isEntry).fileName; - - function generateCspHashValue(text) { - const hash = sha256.update(text).arrayBuffer(); - const base64 = Buffer.from(hash).toString("base64"); - return `'sha256-${base64}'`; - } - const inlineScripts = [ - `window.OPENCHAT_WEBSITE_VERSION = "${version}";`, + `window.OC_WEBSITE_VERSION = "${version}";`, `var parcelRequire;`, ]; - const cspHashValues = inlineScripts.map(generateCspHashValue); - let csp = ` - style-src * 'unsafe-inline'; - style-src-elem * 'unsafe-inline'; - font-src 'self' https://fonts.gstatic.com/; - object-src 'none'; - base-uri 'self'; - form-action 'self'; - upgrade-insecure-requests; - script-src 'self' 'unsafe-eval' https://scripts.wobbl3.com/ https://api.rollbar.com/api/ https://platform.twitter.com/ https://www.googletagmanager.com/ ${cspHashValues.join( - " ", - )}`; - if (development) { - csp += " http://localhost:* http://127.0.0.1:*"; - } + const csp = generateCspForScripts(inlineScripts); + // TODO this is a duplicate of the index.html file, we should + // have only one source for our index html. return ` @@ -388,28 +239,11 @@ export default { }, }), - // In dev mode, watch for changes to the worker and push sw - watch && watchExternalFiles(), - - // In dev mode, call `npm run start` once - // the bundle has been generated - watch && serve(), - - // Watch the `public` directory and refresh the - // browser on changes when not in production - watch && - livereload({ - watch: "build", - delay: 1000, - }), - - // If we're building for production (npm run build + // We're building for production (npm run build // instead of npm run dev), minify - production && terser(), - - production && analyze({ summaryOnly: true }), - - production && filesize(), + terser(), + analyze({ summaryOnly: true }), + filesize(), // Pull in the worker and service worker copy({ @@ -431,7 +265,3 @@ export default { clearScreen: false, }, }; - -function maybeStringify(value) { - return value !== undefined ? JSON.stringify(value) : undefined; -} diff --git a/frontend/app/rollup.extras.mjs b/frontend/app/rollup.extras.mjs new file mode 100644 index 0000000000..cb3e34ceca --- /dev/null +++ b/frontend/app/rollup.extras.mjs @@ -0,0 +1,155 @@ +//* Extra functionality provided to Rollup and Vite for prod/test/dev builds! +import fs from "fs-extra"; +import dotenv from "dotenv"; +import path, { dirname } from "path"; +import { fileURLToPath } from 'url'; +import { sha256 } from "js-sha256"; + +const __filename = fileURLToPath(import.meta.url); +export const __dirname = dirname(__filename); + +// Sass relevant files & directives +export const mixins = path.join(__dirname, "src", "styles", "mixins.scss"); +export const sassModulesAndMixins = `@use 'sass:math'; @use 'sass:map'; @use '${mixins}' as *;` + +// Generates content security policy (CSP) hash for the provided entry +function generateCspHashValue(text) { + const hash = sha256.update(text).arrayBuffer(); + const base64 = Buffer.from(hash).toString("base64"); + return `'sha256-${base64}'`; +} + +export function generateCspForScripts(inlineScripts, development) { + const cspHashValues = inlineScripts.map(generateCspHashValue); + return ` + style-src * 'unsafe-inline'; + style-src-elem * 'unsafe-inline'; + font-src 'self' https://fonts.gstatic.com/; + object-src 'none'; + base-uri 'self'; + form-action 'self'; + upgrade-insecure-requests; + script-src 'self' 'unsafe-eval' https://scripts.wobbl3.com/ https://api.rollbar.com/api/ https://platform.twitter.com/ https://www.googletagmanager.com/ ${cspHashValues.join( + " " + )}` + (development ? " http://localhost:* http://127.0.0.1:*" : ""); +} + +// Set up environment +export function initEnv() { + dotenv.config({ path: path.join(__dirname, "../.env") }); + + const dfxNetwork = process.env.OC_DFX_NETWORK; + console.log("OC_DFX_NETWORK: ", dfxNetwork); + + if (dfxNetwork) { + const dfxJsonPath = path.join(__dirname, "../..", "dfx.json"); + const dfxJson = JSON.parse(fs.readFileSync(dfxJsonPath)); + const canisterPath = + dfxJson["networks"][dfxNetwork]["type"] === "persistent" + ? path.join(__dirname, "../..", "canister_ids.json") + : path.join(__dirname, "../..", ".dfx", dfxNetwork, "canister_ids.json"); + + if (fs.existsSync(canisterPath)) { + const canisters = JSON.parse(fs.readFileSync(canisterPath)); + process.env.OC_TRANSLATIONS_CANISTER = canisters.translations[dfxNetwork]; + process.env.OC_USER_INDEX_CANISTER = canisters.user_index[dfxNetwork]; + process.env.OC_GROUP_INDEX_CANISTER = canisters.group_index[dfxNetwork]; + process.env.OC_NOTIFICATIONS_CANISTER = canisters.notifications_index[dfxNetwork]; + process.env.OC_IDENTITY_CANISTER = canisters.identity[dfxNetwork]; + process.env.OC_ONLINE_CANISTER = canisters.online_users[dfxNetwork]; + process.env.OC_PROPOSALS_BOT_CANISTER = canisters.proposals_bot[dfxNetwork]; + process.env.OC_AIRDROP_BOT_CANISTER = canisters.airdrop_bot[dfxNetwork]; + process.env.OC_STORAGE_INDEX_CANISTER = canisters.storage_index[dfxNetwork]; + process.env.OC_REGISTRY_CANISTER = canisters.registry[dfxNetwork]; + process.env.OC_MARKET_MAKER_CANISTER = canisters.market_maker[dfxNetwork]; + process.env.OC_SIGN_IN_WITH_EMAIL_CANISTER = canisters.sign_in_with_email[dfxNetwork]; + process.env.OC_SIGN_IN_WITH_ETHEREUM_CANISTER = canisters.sign_in_with_ethereum[dfxNetwork]; + process.env.OC_SIGN_IN_WITH_SOLANA_CANISTER = canisters.sign_in_with_solana[dfxNetwork]; + + console.log("TranslationsCanisterId: ", process.env.OC_TRANSLATIONS_CANISTER); + console.log("UserIndexCanisterId: ", process.env.OC_USER_INDEX_CANISTER); + console.log("GroupIndexCanisterId: ", process.env.OC_GROUP_INDEX_CANISTER); + console.log("NotificationsCanisterId: ", process.env.OC_NOTIFICATIONS_CANISTER); + console.log("IdentityCanisterId: ", process.env.OC_IDENTITY_CANISTER); + console.log("OnlineCanisterId: ", process.env.OC_ONLINE_CANISTER); + console.log("ProposalsBotCanisterId: ", process.env.OC_PROPOSALS_BOT_CANISTER); + console.log("AirdropBotCanisterId: ", process.env.OC_AIRDROP_BOT_CANISTER); + console.log("StorageIndex: ", process.env.OC_STORAGE_INDEX_CANISTER); + console.log("Registry: ", process.env.OC_REGISTRY_CANISTER); + console.log("MarketMaker: ", process.env.OC_MARKET_MAKER_CANISTER); + console.log("SignInWithEmail: ", process.env.OC_SIGN_IN_WITH_EMAIL_CANISTER); + console.log("SignInWithEthereum: ", process.env.OC_SIGN_IN_WITH_ETHEREUM_CANISTER); + console.log("SignInWithSolana: ", process.env.OC_SIGN_IN_WITH_SOLANA_CANISTER); + } else { + console.log( + "Couldn't find canisters JSON at: ", + canisterPath, + ". Falling back to original env vars.", + ); + } + } else { + console.log( + "OC_DFX_NETWORK env var not set, cannot load correct canisterIds, falling back to original env vars.", + ); + } + + const build_env = process.env.OC_BUILD_ENV; + const production = build_env === "production"; + const development = build_env === "development"; + const env = process.env.NODE_ENV ?? (development ? "development" : "production"); + const version = process.env.OC_WEBSITE_VERSION; + + if (!development && !version) { + throw Error("OC_WEBSITE_VERSION environment variable not set"); + } + if (production && !process.env.OC_ROLLBAR_ACCESS_TOKEN) { + throw Error("OC_ROLLBAR_ACCESS_TOKEN environment variable not set"); + } + if (production && !process.env.OC_USERGEEK_APIKEY) { + throw Error("OC_USERGEEK_APIKEY environment variable not set"); + } + if (production && !process.env.OC_METERED_APIKEY) { + throw Error("OC_METERED_APIKEY environment variable not set"); + } + + process.env.OC_SERVICE_WORKER_PATH = `/service_worker.js?v=${version}`; + + console.log("BUILD_ENV", build_env); + console.log("ENV", env); + console.log("OC_INTERNET IDENTITY URL", process.env.OC_INTERNET_IDENTITY_URL); + console.log("OC_INTERNET IDENTITY CANISTER", process.env.OC_INTERNET_IDENTITY_CANISTER_ID); + console.log("OC_NFID URL", process.env.OC_NFID_URL); + console.log("OC_VERSION", version ?? "undefined"); + console.log("OC_SERVICE WORKER PATH", process.env.OC_SERVICE_WORKER_PATH); + + return { + env, + build_env, + production, + development, + version, + dfxNetwork, + } +} + +// Put external dependencies into their own bundle so that they get cached separately +export function manualChunks(id) { + if (id.includes("node_modules")) { + return "vendor"; + } +} + +export function copyFile(fromPath, toPath, file) { + const from = path.join(__dirname, fromPath, file); + const to = path.join(__dirname, toPath, file); + if (fs.existsSync(from)) { + console.log("Copying file -> : ", from, to); + fs.copySync(from, to, { + recursive: true, + }); + } +} + +export function maybeStringify(value) { + return value !== undefined ? JSON.stringify(value) : undefined; +} diff --git a/frontend/app/src/components/App.svelte b/frontend/app/src/components/App.svelte index 413c381abe..3625e2de31 100644 --- a/frontend/app/src/components/App.svelte +++ b/frontend/app/src/components/App.svelte @@ -1,17 +1,19 @@ -
+
diff --git a/frontend/app/src/components/home/access/DiamondGateEvaluator.svelte b/frontend/app/src/components/home/access/DiamondGateEvaluator.svelte index d18a56ac74..37f3a034a3 100644 --- a/frontend/app/src/components/home/access/DiamondGateEvaluator.svelte +++ b/frontend/app/src/components/home/access/DiamondGateEvaluator.svelte @@ -31,7 +31,7 @@ }; let ledger: string = - process.env.NODE_ENV === "production" ? LEDGER_CANISTER_CHAT : LEDGER_CANISTER_ICP; + import.meta.env.OC_NODE_ENV === "production" ? LEDGER_CANISTER_CHAT : LEDGER_CANISTER_ICP; function onBalanceRefreshed() { error = undefined; @@ -94,6 +94,8 @@