diff --git a/.gitignore b/.gitignore index 0e82613dfd..38453a6532 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ dex/testing/loadbot/loadbot bin/ bin-v*/ client/webserver/site/template-builder/template-builder +client/webserver/site/webpack-build-id.txt dex/testing/btc/harnesschain.tar.gz client/asset/btc/electrum/example/server/server client/asset/btc/electrum/example/wallet/wallet diff --git a/client/app/config.go b/client/app/config.go index f2aadbc687..8c19be8755 100644 --- a/client/app/config.go +++ b/client/app/config.go @@ -162,7 +162,7 @@ type Config struct { // Web creates a configuration for the webserver. This is a Config method // instead of a WebConfig method because Language is an app-level setting used // by both core and rpcserver. -func (cfg *Config) Web(c *core.Core, mm *mm.MarketMaker, log dex.Logger, utc bool) *webserver.Config { +func (cfg *Config) Web(c *core.Core, mm *mm.MarketMaker, log dex.Logger, utc bool, appVersion string) *webserver.Config { addr := cfg.WebAddr host, _, err := net.SplitHostPort(addr) if err == nil && host != "" { @@ -196,6 +196,7 @@ func (cfg *Config) Web(c *core.Core, mm *mm.MarketMaker, log dex.Logger, utc boo NoEmbed: cfg.NoEmbedSite, HttpProf: cfg.HTTPProfile, Language: cfg.Language, + AppVersion: appVersion, } } @@ -348,6 +349,7 @@ func ResolveConfig(appData string, cfg *Config) error { if cfg.MMConfig.EventLogDBPath == "" { cfg.MMConfig.EventLogDBPath = defaultMMEventLogDBPath } + return nil } diff --git a/client/cmd/bisonw-desktop/app.go b/client/cmd/bisonw-desktop/app.go index 327525e5b3..8efd6fafcd 100644 --- a/client/cmd/bisonw-desktop/app.go +++ b/client/cmd/bisonw-desktop/app.go @@ -222,7 +222,7 @@ func mainCore() error { }() } - webSrv, err := webserver.New(cfg.Web(clientCore, marketMaker, logMaker.Logger("WEB"), utc)) + webSrv, err := webserver.New(cfg.Web(clientCore, marketMaker, logMaker.Logger("WEB"), utc, app.Version)) if err != nil { return fmt.Errorf("failed creating web server: %w", err) } diff --git a/client/cmd/bisonw/main.go b/client/cmd/bisonw/main.go index 01e7c94065..065160d832 100644 --- a/client/cmd/bisonw/main.go +++ b/client/cmd/bisonw/main.go @@ -154,7 +154,7 @@ func runCore(cfg *app.Config) error { } if !cfg.NoWeb { - webSrv, err := webserver.New(cfg.Web(clientCore, marketMaker, logMaker.Logger("WEB"), utc)) + webSrv, err := webserver.New(cfg.Web(clientCore, marketMaker, logMaker.Logger("WEB"), utc, app.Version)) if err != nil { return fmt.Errorf("failed creating web server: %w", err) } diff --git a/client/webserver/http.go b/client/webserver/http.go index 45ca372e66..56f533fed0 100644 --- a/client/webserver/http.go +++ b/client/webserver/http.go @@ -260,6 +260,7 @@ func (s *WebServer) handleSettings(w http.ResponseWriter, r *http.Request) { FiatCurrency string Exchanges map[string]*core.Exchange IsInitialized bool + AppVersion string }{ CommonArguments: *common, KnownExchanges: s.knownUnregisteredExchanges(xcs), @@ -267,6 +268,7 @@ func (s *WebServer) handleSettings(w http.ResponseWriter, r *http.Request) { FiatRateSources: s.core.FiatRateSources(), Exchanges: xcs, IsInitialized: s.core.IsInitialized(), + AppVersion: s.appVersion, } s.sendTemplate(w, "settings", data) } diff --git a/client/webserver/locales/ar.go b/client/webserver/locales/ar.go index 27f7c264ac..136dd42e7d 100644 --- a/client/webserver/locales/ar.go +++ b/client/webserver/locales/ar.go @@ -137,7 +137,7 @@ var Ar = map[string]*intl.Translation{ "Export Account": {T: "تصدير الحساب"}, "simultaneous_servers_msg": {T: "يدعم عميل منصة المبادلات اللامركزية لديكريد الاستخدام المتزامن لأي عدد من خوادم منصة المبادلات اللامركزية DEX."}, "Change App Password": {T: "تغيير كلمة مرور التطبيق"}, - "Build ID": {T: "معرف البنية"}, + "Version": {T: "معرف البنية"}, "Connect": {T: "اتصل"}, "Send": {T: "ارسال"}, "Deposit": {T: "إيداع"}, diff --git a/client/webserver/locales/de-de.go b/client/webserver/locales/de-de.go index ecd2becfea..bcf462d8de 100644 --- a/client/webserver/locales/de-de.go +++ b/client/webserver/locales/de-de.go @@ -135,7 +135,7 @@ var DeDE = map[string]*intl.Translation{ "Export Account": {T: "Account exportieren"}, "simultaneous_servers_msg": {T: "Der unterstützt die gleichzeitige Nutzung einer beliebigen Anzahl von DEX-Servern."}, "Change App Password": {T: "App-Passwort ändern"}, - "Build ID": {T: "Build ID"}, + "Version": {T: "Version"}, "Connect": {T: "Verbinden"}, "Send": {T: "Senden"}, "Deposit": {T: "Einzahlen"}, diff --git a/client/webserver/locales/en-us.go b/client/webserver/locales/en-us.go index 8d15eef0cc..799727bfe4 100644 --- a/client/webserver/locales/en-us.go +++ b/client/webserver/locales/en-us.go @@ -167,7 +167,7 @@ var EnUS = map[string]*intl.Translation{ "browser_ntfn_blocked": {T: "Browser notifications are currently blocked. Please unblock this site in your browser to receive notifications."}, "enable_browser_ntfn_info": {T: "Desktop notifications appear even when this window is not active. When you have other applications open this can be helpful as you will be notified on DCRDEX events. Customize below the types of notifications you would like to receive."}, "Save Notifications": {T: "Save Notifications"}, - "Build ID": {T: "Build ID"}, + "Version": {T: "Version"}, "Connect": {T: "Connect"}, "Send": {T: "Send"}, "Deposit": {T: "Deposit"}, // unused diff --git a/client/webserver/locales/pl-pl.go b/client/webserver/locales/pl-pl.go index c1f7be9a99..fa8898c193 100644 --- a/client/webserver/locales/pl-pl.go +++ b/client/webserver/locales/pl-pl.go @@ -133,7 +133,7 @@ var PlPL = map[string]*intl.Translation{ "Export Account": {T: "Eksportuj konto"}, "simultaneous_servers_msg": {T: "Klient Decred DEX wspiera jednoczesne korzystanie z wielu serwerów DEX."}, "Change App Password": {T: "Zmień hasło aplikacji"}, - "Build ID": {T: "ID builda"}, + "Version": {T: "Wersja"}, "Connect": {T: "Połącz"}, "Withdraw": {T: "Wypłać"}, "Deposit": {T: "Zdeponuj"}, diff --git a/client/webserver/locales/pt-br.go b/client/webserver/locales/pt-br.go index 38187ed0b2..cb9c96c6e5 100644 --- a/client/webserver/locales/pt-br.go +++ b/client/webserver/locales/pt-br.go @@ -133,7 +133,7 @@ var PtBr = map[string]*intl.Translation{ "Export Account": {T: "Exportar Conta"}, "simultaneous_servers_msg": {T: "O cliente da DEX suporta simultâneos números de servidores DEX."}, "Change App Password": {T: "Trocar Senha do aplicativo"}, - "Build ID": {T: "ID da Build"}, + "Version": {T: "Versão"}, "Connect": {T: "Conectar"}, "Withdraw": {T: "Retirar"}, "Deposit": {T: "Depositar"}, diff --git a/client/webserver/locales/zh-cn.go b/client/webserver/locales/zh-cn.go index 0b68f012b7..01194cf367 100644 --- a/client/webserver/locales/zh-cn.go +++ b/client/webserver/locales/zh-cn.go @@ -135,7 +135,7 @@ var ZhCN = map[string]*intl.Translation{ "Export Account": {T: "退出账户"}, "simultaneous_servers_msg": {T: "Decred DEX 客户端支持同时使用任意数量的 DEX 服务器。"}, "Change App Password": {T: "更改应用程序密码"}, - "Build ID": {T: "构建 ID"}, + "Version": {T: "版本"}, "Connect": {T: "连接"}, "Send": {T: "发送"}, "Deposit": {T: "存款"}, diff --git a/client/webserver/site/src/html/bodybuilder.tmpl b/client/webserver/site/src/html/bodybuilder.tmpl index fb0fca5e3d..8fa851e01a 100644 --- a/client/webserver/site/src/html/bodybuilder.tmpl +++ b/client/webserver/site/src/html/bodybuilder.tmpl @@ -9,7 +9,7 @@ {{.Title}} - + - + {{end}} diff --git a/client/webserver/site/src/html/settings.tmpl b/client/webserver/site/src/html/settings.tmpl index e99927a64b..48d992fb91 100644 --- a/client/webserver/site/src/html/settings.tmpl +++ b/client/webserver/site/src/html/settings.tmpl @@ -79,7 +79,8 @@ -

[[[Build ID]]]:

+ {{$version := .AppVersion}} +

[[[Version]]]: {{$version}}

diff --git a/client/webserver/site/src/js/app.ts b/client/webserver/site/src/js/app.ts index aa035b90f0..642af9a56d 100644 --- a/client/webserver/site/src/js/app.ts +++ b/client/webserver/site/src/js/app.ts @@ -161,7 +161,7 @@ export default class Application { authed: boolean user: User seedGenTime: number - commitHash: string + webpackBuildID: string showPopups: boolean loggers: Record recorders: Record @@ -185,14 +185,14 @@ export default class Application { this.notes = [] this.pokes = [] this.seedGenTime = 0 - this.commitHash = process.env.COMMITHASH || '' + this.webpackBuildID = process.env.WEBPACK_BUILD_ID || '' this.noteReceivers = [] this.fiatRatesMap = {} this.showPopups = State.fetchLocal(State.popupsLK) === '1' this.txHistoryMap = {} this.requiredActions = {} - console.log('Bison Wallet, Build', this.commitHash.substring(0, 7)) + console.log('Bison Wallet', 'Webpack Build Id:', this.webpackBuildID) // Set Bootstrap dark theme attribute if dark mode is enabled. if (State.isDark()) { @@ -261,7 +261,7 @@ export default class Application { // Don't fetch the user until we know what page we're on. await this.fetchUser() const ignoreCachedLocale = process.env.NODE_ENV === 'development' - await intl.loadLocale(this.lang, this.commitHash, ignoreCachedLocale) + await intl.loadLocale(this.lang, this.webpackBuildID, ignoreCachedLocale) // The application is free to respond with a page that differs from the // one requested in the omnibox, e.g. routing though a login page. Set the // current URL state based on the actual page. diff --git a/client/webserver/site/src/js/locales.ts b/client/webserver/site/src/js/locales.ts index ff7b927234..47f3abf4fd 100644 --- a/client/webserver/site/src/js/locales.ts +++ b/client/webserver/site/src/js/locales.ts @@ -222,16 +222,16 @@ export const ID_DELETE_BOT = 'DELETE_BOT' let locale: Locale -export async function loadLocale (lang: string, commitHash: string, skipCache: boolean) { +export async function loadLocale (lang: string, buildID: string, skipCache: boolean) { if (!skipCache) { const specs = State.fetchLocal(State.localeSpecsKey) - if (specs && specs.lang === lang && specs.commitHash === commitHash) { + if (specs && specs.lang === lang && specs.webpackBuildID === buildID) { // not stale locale = State.fetchLocal(State.localeKey) return } } locale = await postJSON('/api/locale', lang) - State.storeLocal(State.localeSpecsKey, { lang, commitHash }) + State.storeLocal(State.localeSpecsKey, { lang, buildID }) State.storeLocal(State.localeKey, locale) } diff --git a/client/webserver/site/src/js/registry.ts b/client/webserver/site/src/js/registry.ts index d4832abee4..bc4bcaa680 100644 --- a/client/webserver/site/src/js/registry.ts +++ b/client/webserver/site/src/js/registry.ts @@ -1275,7 +1275,7 @@ export interface Application { exchanges: Record fiatRatesMap: Record showPopups: boolean - commitHash: string + webpackBuildID: string authed: boolean start (): Promise reconnected (): void diff --git a/client/webserver/site/src/js/settings.ts b/client/webserver/site/src/js/settings.ts index 5f973de5de..66a48a2a51 100644 --- a/client/webserver/site/src/js/settings.ts +++ b/client/webserver/site/src/js/settings.ts @@ -61,7 +61,6 @@ export default class SettingsPage extends BasePage { app().showPopups = show }) - page.commitHash.textContent = app().commitHash.substring(0, 7) Doc.bind(page.addADex, 'click', () => { this.dexAddrForm.refresh() this.showForm(page.dexAddrForm) diff --git a/client/webserver/site/webpack/analyze.js b/client/webserver/site/webpack/analyze.js index a87cdb414e..185e1d3251 100644 --- a/client/webserver/site/webpack/analyze.js +++ b/client/webserver/site/webpack/analyze.js @@ -8,7 +8,7 @@ module.exports = merge(common, { rules: [{ test: /\.ts$/, use: 'ts-loader', - exclude: /node_modules/, + exclude: /node_modules/ }] }, optimization: { diff --git a/client/webserver/site/webpack/common.js b/client/webserver/site/webpack/common.js index 985000c145..608a477e20 100644 --- a/client/webserver/site/webpack/common.js +++ b/client/webserver/site/webpack/common.js @@ -5,13 +5,24 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin') const StyleLintPlugin = require('stylelint-webpack-plugin') const ESLintPlugin = require('eslint-webpack-plugin') -const child_process = require('child_process') -function git(command) { - return child_process.execSync(`git ${command}`, { encoding: 'utf8' }).trim(); +const fs = require('node:fs') +const buildIdFilename = 'webpack-build-id.txt' + +function randBuildId () { + const buildID = JSON.stringify(Math.floor(Math.random() * 1000000000)).trim() + console.log('WEBPACK_BUILD_ID:', buildID) + fs.writeFile(buildIdFilename, buildID, err => { + if (err) { + console.error(err) + } else { + console.log(' ', buildID, ' written to ', buildIdFilename) + } + }) + return buildID } module.exports = { - target: "web", + target: 'web', module: { rules: [ { @@ -29,7 +40,7 @@ module.exports = { { loader: 'sass-loader', options: { - implementation: require("sass"), // dart-sass + implementation: require('sass'), // dart-sass sourceMap: true } } @@ -38,15 +49,15 @@ module.exports = { ] }, plugins: [ - new webpack.EnvironmentPlugin({ - COMMITHASH: git('rev-parse HEAD'), + new webpack.DefinePlugin({ + 'process.env.WEBPACK_BUILD_ID': randBuildId() }), new CleanWebpackPlugin(), new MiniCssExtractPlugin({ filename: '../dist/style.css' }), new StyleLintPlugin({ - threads: true, + threads: true }), new ESLintPlugin({ extensions: ['ts'], @@ -59,7 +70,7 @@ module.exports = { publicPath: '/dist/' }, resolve: { - extensions: ['.ts', ".js"], + extensions: ['.ts', '.js'] }, // Fixes weird issue with watch script. See // https://github.com/webpack/webpack/issues/2297#issuecomment-289291324 diff --git a/client/webserver/site/webpack/dev.js b/client/webserver/site/webpack/dev.js index dbfa29db19..d4fdf226a6 100644 --- a/client/webserver/site/webpack/dev.js +++ b/client/webserver/site/webpack/dev.js @@ -7,7 +7,7 @@ module.exports = merge(common, { rules: [{ test: /\.ts$/, use: 'ts-loader', - exclude: /node_modules/, + exclude: /node_modules/ }] }, devtool: 'inline-source-map' diff --git a/client/webserver/site/webpack/prod.js b/client/webserver/site/webpack/prod.js index e64ddd5137..2e42708116 100644 --- a/client/webserver/site/webpack/prod.js +++ b/client/webserver/site/webpack/prod.js @@ -9,7 +9,7 @@ module.exports = merge(common, { usedExports: true, minimize: true, minimizer: [ - `...`, // extend webpack 5's TerserPlugin + '...', // extend webpack 5's TerserPlugin new CssMinimizerPlugin({}) ] }, diff --git a/client/webserver/template.go b/client/webserver/template.go index 0dba710eed..c7d12b3f38 100644 --- a/client/webserver/template.go +++ b/client/webserver/template.go @@ -5,22 +5,21 @@ package webserver import ( "bytes" - "encoding/hex" "fmt" "html/template" "io/fs" "os" + "path/filepath" "strings" - "runtime/debug" - "decred.org/dcrdex/client/intl" "decred.org/dcrdex/client/webserver/locales" - "decred.org/dcrdex/dex/encode" "golang.org/x/text/cases" "golang.org/x/text/language" ) +const webpackBuildIdFile = "webpack-build-id.txt" + // pageTemplate holds the information necessary to process a template. Also // holds information necessary to reload the templates for development. type pageTemplate struct { @@ -188,16 +187,35 @@ func (t *templates) exec(name string, data any) (string, error) { return page.String(), err } -var commit = func() string { - if info, ok := debug.ReadBuildInfo(); ok { - for _, setting := range info.Settings { - if setting.Key == "vcs.revision" && len(setting.Value) >= 8 { - return setting.Value - } - } +// webpackBuildIdQuery fetches latest webpack build from the webpack-build-id.txt +// file in app site directory and makes it available to append to the main css +// link and to the main script link in bodybuilder; this should cause no reload +// of the main css/js files if they are already cached by the browser. +// If webpackBuildIdFile is not found return a fallback query that will make the +// browser reload css/js. +var webpackBuildIdQuery = func() string { + var fallbackQueryStr = "?v=1" + d, _ := os.Getwd() + cwd := filepath.Base(d) + if cwd != "bisonw" { + return fallbackQueryStr } - - return hex.EncodeToString(encode.RandomBytes(4)) + d = filepath.Dir(d) + cmd := filepath.Base(d) + if cmd != "cmd" { + return fallbackQueryStr + } + d = filepath.Dir(d) + client := filepath.Base(d) + if client != "client" { + return fallbackQueryStr + } + f := filepath.Join(d, "/webserver/site", webpackBuildIdFile) + wpB, err := os.ReadFile(f) + if err != nil { + return fallbackQueryStr + } + return "?v=" + string(wpB) }() // templateFuncs are able to be called during template execution. @@ -228,7 +246,7 @@ var templateFuncs = template.FuncMap{ } return parts[0] }, - "commitHash": func() string { - return commit[:8] + "webpackBuildIdQuery": func() string { + return webpackBuildIdQuery }, } diff --git a/client/webserver/webserver.go b/client/webserver/webserver.go index 1ea09fa6fe..afed803691 100644 --- a/client/webserver/webserver.go +++ b/client/webserver/webserver.go @@ -238,8 +238,9 @@ type Config struct { // should be used by default since site files from older distributions may // be present on the disk. When NoEmbed is true, this also implies reloading // and execution of html templates on each request. - NoEmbed bool - HttpProf bool + NoEmbed bool + HttpProf bool + AppVersion string } type valStamp struct { @@ -271,6 +272,7 @@ type WebServer struct { bondBuf map[uint32]valStamp useDEXBranding bool + appVersion string } // New is the constructor for a new WebServer. CustomSiteDir in the Config can @@ -392,6 +394,7 @@ func New(cfg *Config) (*WebServer, error) { cachedPasswords: make(map[string]*cachedPassword), bondBuf: map[uint32]valStamp{}, useDEXBranding: useDEXBranding, + appVersion: cfg.AppVersion, } s.lang.Store(lang)