Skip to content

Commit f185259

Browse files
committed
feat(demo): enhance demo build configuration and streamline initialization logic
1 parent a25e044 commit f185259

File tree

5 files changed

+205
-90
lines changed

5 files changed

+205
-90
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
"packageManager": "yarn@1.22.22",
77
"scripts": {
88
"serve": "yarn pre && yarn vue-cli-service serve --port 8081",
9+
"serve:demo": "yarn pre && build=demo yarn vue-cli-service serve --port 8081",
910
"build": "yarn pre && yarn vue-cli-service build",
11+
"build:demo": "yarn pre && build=demo NODE_ENV=production yarn vue-cli-service build",
12+
"build:demo-production": "yarn pre && build=demo-production NODE_ENV=production yarn vue-cli-service build",
1013
"lint": "vue-cli-service lint --no-fix",
1114
"lint:fix": "vue-cli-service lint",
1215
"build:bitcoinjs": "yarn --silent browserify bitcoinjs-parts.js -p common-shakeify -s BitcoinJS | yarn terser --compress --mangle --source-map --output public/bitcoin/BitcoinJS.min.js",

src/lib/Demo.ts

Lines changed: 3 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,6 @@ export type DemoState = {
5151
active: boolean,
5252
};
5353

54-
// The query param that activates the demo. e.g. https://wallet.nimiq.com/?demo=
55-
const DEMO_PARAM = 'demo';
56-
5754
// No additional import needed, Config is already imported above
5855

5956
const DemoFallbackModal = () =>
@@ -136,28 +133,11 @@ export function dangerouslyInitializeDemo(router: VueRouter) {
136133
// #region App setup
137134

138135
/**
139-
* Checks if the demo mode should be active based on the URL param and configuration.
140-
* Demo mode is active if:
141-
* 1. The demo query param is present in the URL, OR
142-
* 2. The Config.demo.enabled is true, OR
143-
* 3. The Config.demo.enabled is a string that matches the current hostname
136+
* Checks if the demo mode should be active.
137+
* In demo builds, this always returns true since demo builds are separate deployments.
144138
*/
145139
export function checkIfDemoIsActive() {
146-
// Always check URL param first - this allows demo mode to be forced on any instance
147-
if (window.location.search.includes(DEMO_PARAM)) return true;
148-
149-
// Check configuration - can be boolean or string (hostname)
150-
const demoConfig = Config.demo?.enabled;
151-
152-
if (typeof demoConfig === 'boolean') {
153-
return demoConfig;
154-
}
155-
156-
if (typeof demoConfig === 'string') {
157-
return window.location.hostname === demoConfig;
158-
}
159-
160-
return false;
140+
return true;
161141
}
162142

163143
/**
@@ -345,14 +325,8 @@ function attachIframeListeners() {
345325
if (kind === MessageEventName.FlowChange && demoRoutes[data]) {
346326
useAccountStore().setActiveCurrency(CryptoCurrency.NIM);
347327

348-
// Only include demo parameter in query if it's present in the current URL
349-
const query = window.location.search.includes(DEMO_PARAM)
350-
? { [DEMO_PARAM]: '' }
351-
: undefined;
352-
353328
demoRouter.push({
354329
path: demoRoutes[data],
355-
query,
356330
});
357331
}
358332
});
@@ -1004,13 +978,8 @@ function replaceBuyNimFlow() {
1004978
btn1.className = 'nq-button-s inverse';
1005979
btn1.style.flex = '1';
1006980
btn1.addEventListener('click', () => {
1007-
// Only include demo parameter in query if it's present in the current URL
1008-
const query = window.location.search.includes(DEMO_PARAM)
1009-
? { [DEMO_PARAM]: '' }
1010-
: undefined;
1011981
demoRouter.push({
1012982
path: '/buy',
1013-
query,
1014983
});
1015984
});
1016985
btn1.innerHTML = 'Buy';
@@ -1062,14 +1031,8 @@ function replaceStakingFlow() {
10621031

10631032
const { address: validatorAddress } = activeValidator.value!;
10641033

1065-
// Only include demo parameter in query if it's present in the current URL
1066-
const query = window.location.search.includes(DEMO_PARAM)
1067-
? { [DEMO_PARAM]: '' }
1068-
: undefined;
1069-
10701034
demoRouter.push({
10711035
path: '/',
1072-
query,
10731036
});
10741037

10751038
await new Promise<void>((resolve) => { window.setTimeout(resolve, 100); });
@@ -1906,13 +1869,8 @@ export class DemoHubApi extends HubApi {
19061869
});
19071870

19081871
console.log('[Demo] Redirecting to fallback modal');
1909-
// Only include demo parameter in query if it's present in the current URL
1910-
const query = window.location.search.includes(DEMO_PARAM)
1911-
? { [DEMO_PARAM]: '' }
1912-
: undefined;
19131872
demoRouter.push({
19141873
path: `/${DemoModal.Fallback}`,
1915-
query,
19161874
});
19171875
});
19181876
},
@@ -2019,15 +1977,9 @@ function observeReceiveModal(processedElements: WeakSet<Element>) {
20191977
event.preventDefault();
20201978
event.stopPropagation();
20211979

2022-
// Only include demo parameter in query if it's present in the current URL
2023-
const query = window.location.search.includes(DEMO_PARAM)
2024-
? { [DEMO_PARAM]: '' }
2025-
: undefined;
2026-
20271980
// Redirect to the fallback modal
20281981
demoRouter.replace({
20291982
path: `/${DemoModal.Fallback}`,
2030-
query,
20311983
});
20321984

20331985
console.log('[Demo] Redirected receive modal button click to fallback modal');

src/main-demo.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import Vue from 'vue';
2+
import VueCompositionApi, { watch } from '@vue/composition-api';
3+
// @ts-expect-error Could not find a declaration file for module 'vue-virtual-scroller'.
4+
import VueVirtualScroller from 'vue-virtual-scroller';
5+
import { setAssetPublicPath as setVueComponentsAssetPath } from '@nimiq/vue-components';
6+
import { init as initFastspotApi } from '@nimiq/fastspot-api';
7+
// @ts-expect-error missing types for this package
8+
import VuePortal from '@linusborg/vue-simple-portal';
9+
10+
import App from './App.vue';
11+
import { dangerouslyInitializeDemo } from './lib/Demo';
12+
import { useAccountStore } from './stores/Account';
13+
import { useFiatStore } from './stores/Fiat';
14+
import { useSettingsStore } from './stores/Settings';
15+
import router from './router';
16+
import { i18n, loadLanguage } from './i18n/i18n-setup';
17+
import { CryptoCurrency } from './lib/Constants';
18+
import { useConfig } from './composables/useConfig';
19+
import { initPwa } from './composables/usePwaInstallPrompt';
20+
import { useInactivityDetection } from './composables/useInactivityDetection';
21+
22+
// Side-effects
23+
import './lib/AddressBook';
24+
25+
import '@nimiq/style/nimiq-style.min.css';
26+
import '@nimiq/vue-components/dist/NimiqVueComponents.css';
27+
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
28+
import '@/scss/themes.scss';
29+
30+
// Set asset path relative to the public path defined in vue.config.json,
31+
// see https://cli.vuejs.org/guide/mode-and-env.html#using-env-variables-in-client-side-code
32+
setVueComponentsAssetPath(`${process.env.BASE_URL}js/`, `${process.env.BASE_URL}img/`);
33+
34+
Vue.config.productionTip = false;
35+
36+
Vue.use(VueCompositionApi);
37+
Vue.use(VueVirtualScroller);
38+
Vue.use(VuePortal, { name: 'Portal' });
39+
40+
async function startDemo() {
41+
// eslint-disable-next-line no-console
42+
console.warn('[Demo] Starting Nimiq Wallet in demo mode...');
43+
44+
initPwa(); // Must be called as soon as possible to catch early browser events related to PWA
45+
46+
// Initialize demo environment - this replaces the normal storage/hub initialization
47+
dangerouslyInitializeDemo(router);
48+
49+
// Update exchange rates every 2 minutes or every 10 minutes, depending on whether the Wallet is currently actively
50+
// used. If an update takes longer than that time due to a provider's rate limit, wait until the update succeeds
51+
// before queueing the next update. If the last update before page load was less than 2 minutes ago, wait the
52+
// remaining time first.
53+
const { timestamp: lastSuccessfulExchangeRateUpdate, updateExchangeRates } = useFiatStore();
54+
const { isUserInactive } = useInactivityDetection();
55+
let lastTriedExchangeRateUpdate = lastSuccessfulExchangeRateUpdate.value;
56+
const TWO_MINUTES = 2 * 60 * 1000;
57+
const TEN_MINUTES = 5 * TWO_MINUTES;
58+
let exchangeRateUpdateTimer = -1;
59+
function queueExchangeRateUpdate() {
60+
const interval = isUserInactive.value ? TEN_MINUTES : TWO_MINUTES;
61+
// Update lastTriedExchangeRateUpdate as there might have been other exchange rate updates in the meantime, for
62+
// example on currency change.
63+
lastTriedExchangeRateUpdate = Math.max(lastTriedExchangeRateUpdate, lastSuccessfulExchangeRateUpdate.value);
64+
// Also set interval as upper bound to be immune to the user's system clock being wrong.
65+
const remainingTime = Math.max(0, Math.min(lastTriedExchangeRateUpdate + interval - Date.now(), interval));
66+
clearTimeout(exchangeRateUpdateTimer);
67+
exchangeRateUpdateTimer = window.setTimeout(async () => {
68+
// Silently ignore errors. If successful, this updates fiatStore.timestamp, which then also triggers price
69+
// chart updates in PriceChart.vue.
70+
await updateExchangeRates(/* failGracefully */ true);
71+
// In contrast to lastSuccessfulExchangeRateUpdate also update lastTriedExchangeRateUpdate on failed
72+
// attempts, to avoid repeated rescheduling on failure. Instead, simply skip the failed attempt and try
73+
// again at the regular interval. We update the time after the update attempt, instead of before it, because
74+
// exchange rates are up-to-date at the time an update successfully finishes, and get old from that point,
75+
// and not from the time the update was started.
76+
lastTriedExchangeRateUpdate = Date.now();
77+
queueExchangeRateUpdate();
78+
}, remainingTime);
79+
}
80+
watch(isUserInactive, queueExchangeRateUpdate); // (Re)schedule exchange rate updates at the desired interval.
81+
82+
// Fetch language file
83+
const { language } = useSettingsStore();
84+
loadLanguage(language.value);
85+
86+
const { config } = useConfig();
87+
88+
// Set demo-specific document title
89+
document.title = 'Nimiq Wallet Demo';
90+
91+
// Initialize Fastspot API for demo
92+
watch(() => {
93+
if (!config.fastspot.apiEndpoint || !config.fastspot.apiKey) return;
94+
initFastspotApi(config.fastspot.apiEndpoint, config.fastspot.apiKey);
95+
});
96+
97+
// Make reactive config accessible in components
98+
Vue.prototype.$config = config;
99+
100+
new Vue({
101+
router,
102+
i18n,
103+
render: (h) => h(App),
104+
}).$mount('#app');
105+
106+
// Note: We don't launch network, electrum, or polygon connections in demo mode
107+
// as the demo uses simulated data instead of real blockchain connections
108+
109+
// Set active currency to NIM by default for demo
110+
const { state: { activeCurrency } } = useAccountStore();
111+
if (activeCurrency !== CryptoCurrency.NIM) {
112+
useAccountStore().setActiveCurrency(CryptoCurrency.NIM);
113+
}
114+
}
115+
116+
startDemo();
117+
118+
declare module 'vue/types/vue' {
119+
interface Vue {
120+
$config: ReturnType<typeof useConfig>['config'];
121+
}
122+
}
123+
124+
declare module '@vue/composition-api/dist/component/component' {
125+
interface SetupContext {
126+
readonly refs: { [key: string]: Vue | Element | Vue[] | Element[] };
127+
}
128+
}

src/main.ts

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { launchPolygon } from './ethers';
1818
import { initMatomo } from './matomo';
1919
import { useAccountStore } from './stores/Account';
2020
import { useFiatStore } from './stores/Fiat';
21-
import { checkIfDemoIsActive, dangerouslyInitializeDemo } from './lib/Demo';
2221
import { useSettingsStore } from './stores/Settings';
2322
import router from './router';
2423
import { i18n, loadLanguage } from './i18n/i18n-setup';
@@ -51,21 +50,15 @@ Vue.use(VuePortal, { name: 'Portal' });
5150
async function start() {
5251
initPwa(); // Must be called as soon as possible to catch early browser events related to PWA
5352

54-
const isDemoActive = checkIfDemoIsActive();
53+
await initStorage(); // Must be awaited before starting Vue
54+
initTrials(); // Must be called after storage was initialized, can affect Config
55+
// Must run after VueCompositionApi has been enabled and after storage was initialized. Could potentially run in
56+
// background and in parallel to syncFromHub, but RedirectRpcClient.init does not actually run async code
57+
// anyways.
58+
await initHubApi();
59+
syncFromHub(); // Can run parallel to Vue initialization; must be called after storage was initialized.
5560

56-
if (!isDemoActive) {
57-
await initStorage(); // Must be awaited before starting Vue
58-
initTrials(); // Must be called after storage was initialized, can affect Config
59-
// Must run after VueCompositionApi has been enabled and after storage was initialized. Could potentially run in
60-
// background and in parallel to syncFromHub, but RedirectRpcClient.init does not actually run async code
61-
// anyways.
62-
await initHubApi();
63-
syncFromHub(); // Can run parallel to Vue initialization; must be called after storage was initialized.
64-
65-
serviceWorkerHasUpdate.then((hasUpdate) => useSettingsStore().state.updateAvailable = hasUpdate);
66-
} else {
67-
dangerouslyInitializeDemo(router);
68-
}
61+
serviceWorkerHasUpdate.then((hasUpdate) => useSettingsStore().state.updateAvailable = hasUpdate);
6962

7063
// Update exchange rates every 2 minutes or every 10 minutes, depending on whether the Wallet is currently actively
7164
// used. If an update takes longer than that time due to a provider's rate limit, wait until the update succeeds
@@ -104,15 +97,11 @@ async function start() {
10497
const { language } = useSettingsStore();
10598
loadLanguage(language.value);
10699

107-
if (!isDemoActive) {
108-
startSentry();
109-
}
100+
startSentry();
110101

111102
const { config } = useConfig();
112103

113-
if (isDemoActive) {
114-
document.title = 'Nimiq Wallet Demo';
115-
} else if (config.environment !== ENV_MAIN) {
104+
if (config.environment !== ENV_MAIN) {
116105
document.title = 'Nimiq Testnet Wallet';
117106
}
118107

@@ -121,17 +110,15 @@ async function start() {
121110
initFastspotApi(config.fastspot.apiEndpoint, config.fastspot.apiKey);
122111
});
123112

124-
if (!isDemoActive) {
125-
watch(() => {
126-
if (!config.oasis.apiEndpoint) return;
127-
initOasisApi(config.oasis.apiEndpoint);
128-
});
113+
watch(() => {
114+
if (!config.oasis.apiEndpoint) return;
115+
initOasisApi(config.oasis.apiEndpoint);
116+
});
129117

130-
watch(() => {
131-
if (!config.ten31Pass.enabled) return;
132-
initKycConnection();
133-
});
134-
}
118+
watch(() => {
119+
if (!config.ten31Pass.enabled) return;
120+
initKycConnection();
121+
});
135122

136123
watch(() => {
137124
if (!config.matomo.enabled) return;

0 commit comments

Comments
 (0)