Skip to content

Commit 192aafd

Browse files
authored
Merge pull request #6385 from remix-project-org/topbarelectron
Desktop bugs
2 parents 0e31a81 + 4771e05 commit 192aafd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2126
-405
lines changed

apps/remix-ide-e2e/src/commands/hideToolTips.ts

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,49 @@ import { NightwatchBrowser } from 'nightwatch'
22
import EventEmitter from 'events'
33

44
class HideToolTips extends EventEmitter {
5-
command(this: NightwatchBrowser) {
6-
console.log('Hiding tooltips...')
5+
command(this: NightwatchBrowser): NightwatchBrowser {
6+
const browser = this.api
77
browser
8-
.perform((done) => {
9-
browser.execute(function () {
10-
// hide tooltips
11-
function addStyle(styleString) {
12-
const style = document.createElement('style')
13-
style.textContent = styleString
14-
document.head.append(style)
8+
.execute(function () {
9+
// Set global flag to disable all CustomTooltip components
10+
(window as any).REMIX_DISABLE_TOOLTIPS = true
11+
12+
// Dispatch custom event to notify all CustomTooltip components
13+
const event = new CustomEvent('remix-tooltip-toggle', {
14+
detail: { disabled: true }
15+
})
16+
window.dispatchEvent(event)
17+
18+
// Add CSS as backup for any non-CustomTooltip tooltips
19+
const style = document.createElement('style')
20+
style.id = 'nightwatch-disable-tooltips'
21+
style.textContent = `
22+
.tooltip,
23+
.popover,
24+
[role="tooltip"],
25+
[id*="Tooltip"],
26+
[id*="tooltip"] {
27+
display: none !important;
28+
visibility: hidden !important;
29+
opacity: 0 !important;
30+
pointer-events: none !important;
1531
}
16-
addStyle(`
17-
.popover {
18-
display:none !important;
19-
}
20-
#scamDetails {
21-
display:none !important;
22-
}
23-
`)
24-
}, [], done())
25-
})
26-
.perform((done) => {
27-
done()
32+
`
33+
const existing = document.getElementById('nightwatch-disable-tooltips')
34+
if (existing) existing.remove()
35+
document.head.appendChild(style)
36+
37+
// Remove any existing tooltips from DOM
38+
document.querySelectorAll('.tooltip, .popover, [role="tooltip"]').forEach(el => {
39+
try { el.remove() } catch (e) {}
40+
})
41+
}, [])
42+
.pause(100)
43+
.perform(() => {
2844
this.emit('complete')
2945
})
46+
47+
return browser
3048
}
3149
}
3250

apps/remix-ide/src/app/matomo/MatomoAutoInit.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ export async function autoInitializeMatomo(options: MatomoAutoInitOptions): Prom
4444
}
4545

4646
try {
47+
// Check for Electron - always initialize in anonymous mode (no consent needed)
48+
const isElectron = (window as any).electronAPI !== undefined;
49+
if (isElectron) {
50+
log('Electron detected, auto-initializing in anonymous mode (server-side tracking)');
51+
await matomoManager.initialize('anonymous');
52+
await matomoManager.processPreInitQueue();
53+
log('Electron Matomo initialized and pre-init queue processed');
54+
return true;
55+
}
56+
4757
// Check if we should show the consent dialog
4858
const shouldShowDialog = matomoManager.shouldShowConsentDialog(config);
4959

apps/remix-ide/src/app/matomo/MatomoConfig.ts

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* Single source of truth for Matomo site IDs and configuration
55
*/
66

7+
import isElectron from 'is-electron';
78
import { MatomoConfig } from './MatomoManager';
89

910
// ================ DEVELOPER CONFIGURATION ================
@@ -34,7 +35,7 @@ export interface DomainCustomDimensions {
3435
}
3536

3637
// Type for domain keys (single source of truth)
37-
export type MatomotDomain = 'alpha.remix.live' | 'beta.remix.live' | 'remix.ethereum.org' | 'localhost' | '127.0.0.1';
38+
export type MatomotDomain = 'alpha.remix.live' | 'beta.remix.live' | 'remix.ethereum.org' | 'localhost' | '127.0.0.1' | 'electron';
3839

3940
// Type for site ID configuration
4041
export type SiteIdConfig = Record<MatomotDomain, number>;
@@ -54,7 +55,8 @@ export const MATOMO_DOMAINS: SiteIdConfig = {
5455
'beta.remix.live': 2,
5556
'remix.ethereum.org': 3,
5657
'localhost': 5,
57-
'127.0.0.1': 5
58+
'127.0.0.1': 5,
59+
'electron': 4 // Remix Desktop (Electron) app
5860
};
5961

6062
// Bot tracking site IDs (separate databases to avoid polluting human analytics)
@@ -64,7 +66,8 @@ export const MATOMO_BOT_SITE_IDS: BotSiteIdConfig = {
6466
'beta.remix.live': null, // TODO: Create bot tracking site in Matomo (e.g., site ID 11)
6567
'remix.ethereum.org': 8, // TODO: Create bot tracking site in Matomo (e.g., site ID 12)
6668
'localhost': 7, // Keep bots in same localhost site for testing (E2E tests need cookies)
67-
'127.0.0.1': 7 // Keep bots in same localhost site for testing (E2E tests need cookies)
69+
'127.0.0.1': 7, // Keep bots in same localhost site for testing (E2E tests need cookies)
70+
'electron': null // Electron app uses same site ID for bots (filtered via isBot dimension)
6871
};
6972

7073
// Domain-specific custom dimension IDs for HUMAN traffic
@@ -96,6 +99,12 @@ export const MATOMO_CUSTOM_DIMENSIONS: CustomDimensionsConfig = {
9699
trackingMode: 1, // Dimension for 'anon'/'cookie' tracking mode
97100
clickAction: 3, // Dimension for 'true'/'false' click tracking
98101
isBot: 4 // Dimension for 'human'/'bot'/'automation' detection
102+
},
103+
// Electron Desktop App
104+
electron: {
105+
trackingMode: 1, // Dimension for 'anon'/'cookie' tracking mode
106+
clickAction: 2, // Dimension for 'true'/'false' click tracking
107+
isBot: 3 // Dimension for 'human'/'bot'/'automation' detection
99108
}
100109
};
101110

@@ -119,25 +128,40 @@ export const MATOMO_BOT_CUSTOM_DIMENSIONS: BotCustomDimensionsConfig = {
119128
trackingMode: 1,
120129
clickAction: 3,
121130
isBot: 2
122-
}
131+
},
132+
'electron': null // Electron app uses same custom dimensions as human traffic
123133
};
124134

135+
/**
136+
* Get the appropriate domain key for tracking
137+
* Returns 'electron' for Electron app, otherwise returns the hostname
138+
*/
139+
export function getDomainKey(): MatomotDomain {
140+
if (isElectron()) {
141+
return 'electron';
142+
}
143+
144+
const hostname = window.location.hostname as MatomotDomain;
145+
// Return hostname if it's a known domain, otherwise default to localhost
146+
return MATOMO_DOMAINS[hostname] !== undefined ? hostname : 'localhost';
147+
}
148+
125149
/**
126150
* Get the appropriate site ID for the current domain and bot status
127151
*
128152
* @param isBot - Whether the visitor is detected as a bot
129153
* @returns Site ID to use for tracking
130154
*/
131155
export function getSiteIdForTracking(isBot: boolean): number {
132-
const hostname = window.location.hostname;
156+
const domainKey = getDomainKey();
133157

134158
// If bot and bot site ID is configured, use it
135-
if (isBot && MATOMO_BOT_SITE_IDS[hostname] !== null && MATOMO_BOT_SITE_IDS[hostname] !== undefined) {
136-
return MATOMO_BOT_SITE_IDS[hostname];
159+
if (isBot && MATOMO_BOT_SITE_IDS[domainKey] !== null && MATOMO_BOT_SITE_IDS[domainKey] !== undefined) {
160+
return MATOMO_BOT_SITE_IDS[domainKey];
137161
}
138162

139163
// Otherwise use normal site ID
140-
return MATOMO_DOMAINS[hostname] || MATOMO_DOMAINS['localhost'];
164+
return MATOMO_DOMAINS[domainKey];
141165
}
142166

143167
/**
@@ -146,21 +170,15 @@ export function getSiteIdForTracking(isBot: boolean): number {
146170
* @param isBot - Whether the visitor is detected as a bot (to use bot-specific dimensions if configured)
147171
*/
148172
export function getDomainCustomDimensions(isBot: boolean = false): DomainCustomDimensions {
149-
const hostname = window.location.hostname;
173+
const domainKey = getDomainKey();
150174

151175
// If bot and bot-specific dimensions are configured, use them
152-
if (isBot && MATOMO_BOT_CUSTOM_DIMENSIONS[hostname] !== null && MATOMO_BOT_CUSTOM_DIMENSIONS[hostname] !== undefined) {
153-
return MATOMO_BOT_CUSTOM_DIMENSIONS[hostname];
176+
if (isBot && MATOMO_BOT_CUSTOM_DIMENSIONS[domainKey] !== null && MATOMO_BOT_CUSTOM_DIMENSIONS[domainKey] !== undefined) {
177+
return MATOMO_BOT_CUSTOM_DIMENSIONS[domainKey];
154178
}
155179

156180
// Return dimensions for current domain
157-
if (MATOMO_CUSTOM_DIMENSIONS[hostname]) {
158-
return MATOMO_CUSTOM_DIMENSIONS[hostname];
159-
}
160-
161-
// Fallback to localhost if domain not found
162-
console.warn(`No custom dimensions found for domain: ${hostname}, using localhost fallback`);
163-
return MATOMO_CUSTOM_DIMENSIONS['localhost'];
181+
return MATOMO_CUSTOM_DIMENSIONS[domainKey];
164182
}
165183

166184
/**

apps/remix-ide/src/app/matomo/MatomoManager.ts

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,14 @@ export class MatomoManager implements IMatomoManager {
319319
this.emit('log', { message, data, timestamp });
320320
}
321321

322+
/**
323+
* Check if running in Electron environment
324+
*/
325+
private isElectronApp(): boolean {
326+
return typeof window !== 'undefined' &&
327+
(window as any).electronAPI !== undefined;
328+
}
329+
322330
private setupPaqInterception(): void {
323331
this.log('Setting up _paq interception');
324332
if (typeof window === 'undefined') return;
@@ -825,40 +833,63 @@ export class MatomoManager implements IMatomoManager {
825833
trackEvent(eventObjOrCategory: MatomoEvent | string, action?: string, name?: string, value?: string | number): number {
826834
const eventId = ++this.state.lastEventId;
827835

836+
// Extract event parameters
837+
let category: string;
838+
let eventAction: string;
839+
let eventName: string | undefined;
840+
let eventValue: string | number | undefined;
841+
let isClick: boolean | undefined;
842+
828843
// If first parameter is a MatomoEvent object, use type-safe approach
829844
if (typeof eventObjOrCategory === 'object' && eventObjOrCategory !== null && 'category' in eventObjOrCategory) {
830-
const { category, action: eventAction, name: eventName, value: eventValue, isClick } = eventObjOrCategory;
845+
category = eventObjOrCategory.category;
846+
eventAction = eventObjOrCategory.action;
847+
eventName = eventObjOrCategory.name;
848+
eventValue = eventObjOrCategory.value;
849+
isClick = eventObjOrCategory.isClick;
850+
831851
this.log(`Tracking type-safe event ${eventId}: ${category} / ${eventAction} / ${eventName} / ${eventValue} / isClick: ${isClick}`);
852+
} else {
853+
// Legacy string-based approach
854+
category = eventObjOrCategory as string;
855+
eventAction = action!;
856+
eventName = name;
857+
eventValue = value;
832858

833-
// Set custom action dimension for click tracking
834-
if (isClick !== undefined) {
835-
window._paq.push(['setCustomDimension', this.customDimensions.clickAction, isClick ? 'true' : 'false']);
836-
}
859+
this.log(`Tracking legacy event ${eventId}: ${category} / ${eventAction} / ${eventName} / ${eventValue} (⚠️ no click dimension)`);
860+
}
837861

838-
const matomoEvent: MatomoCommand = ['trackEvent', category, eventAction];
839-
if (eventName !== undefined) matomoEvent.push(eventName);
840-
if (eventValue !== undefined) matomoEvent.push(eventValue);
862+
// Check if running in Electron - use IPC bridge instead of _paq
863+
if (this.isElectronApp()) {
864+
this.log(`Electron detected - routing event through IPC bridge`);
841865

842-
window._paq.push(matomoEvent);
843-
this.emit('event-tracked', { eventId, category, action: eventAction, name: eventName, value: eventValue, isClick });
866+
const electronAPI = (window as any).electronAPI;
867+
if (electronAPI && electronAPI.trackEvent) {
868+
// Pass isClick as the 6th parameter
869+
const eventData = ['trackEvent', category, eventAction, eventName || '', eventValue, isClick];
870+
electronAPI.trackEvent(eventData).catch((err: any) => {
871+
console.error('[Matomo] Failed to send event to Electron:', err);
872+
});
873+
}
844874

875+
this.emit('event-tracked', { eventId, category, action: eventAction, name: eventName, value: eventValue, isClick });
845876
return eventId;
846877
}
847878

848-
// Legacy string-based approach - no isClick dimension set
849-
const category = eventObjOrCategory as string;
850-
this.log(`Tracking legacy event ${eventId}: ${category} / ${action} / ${name} / ${value} (⚠️ no click dimension)`);
879+
// Standard web tracking using _paq
880+
if (isClick !== undefined) {
881+
window._paq.push(['setCustomDimension', this.customDimensions.clickAction, isClick ? 'true' : 'false']);
882+
}
851883

852-
const matomoEvent: MatomoCommand = ['trackEvent', category, action!];
853-
if (name !== undefined) matomoEvent.push(name);
854-
if (value !== undefined) matomoEvent.push(value);
884+
const matomoEvent: MatomoCommand = ['trackEvent', category, eventAction];
885+
if (eventName !== undefined) matomoEvent.push(eventName);
886+
if (eventValue !== undefined) matomoEvent.push(eventValue);
855887

856888
window._paq.push(matomoEvent);
857-
this.emit('event-tracked', { eventId, category, action, name, value });
889+
this.emit('event-tracked', { eventId, category, action: eventAction, name: eventName, value: eventValue, isClick });
858890

859891
return eventId;
860892
}
861-
862893
trackPageView(title?: string): void {
863894
this.log(`Tracking page view: ${title || 'default'}`);
864895
const pageView: MatomoCommand = ['trackPageView'];
@@ -948,6 +979,14 @@ export class MatomoManager implements IMatomoManager {
948979
// ================== SCRIPT LOADING ==================
949980

950981
async loadScript(): Promise<void> {
982+
// Skip script loading in Electron - we use IPC bridge instead
983+
if (this.isElectronApp()) {
984+
this.log('Electron detected - skipping Matomo script load (using IPC bridge)');
985+
this.state.scriptLoaded = true;
986+
this.emit('script-loaded');
987+
return;
988+
}
989+
951990
if (this.state.scriptLoaded) {
952991
this.log('Script already loaded');
953992
return;
@@ -1344,11 +1383,16 @@ export class MatomoManager implements IMatomoManager {
13441383
*/
13451384
shouldShowConsentDialog(configApi?: any): boolean {
13461385
try {
1386+
// Electron doesn't need cookie consent (uses server-side HTTP tracking)
1387+
const isElectron = (window as any).electronAPI !== undefined;
1388+
if (isElectron) {
1389+
return false;
1390+
}
1391+
13471392
// Use domains from constructor config or fallback to empty object
13481393
const matomoDomains = this.config.matomoDomains || {};
13491394

1350-
const isElectron = (window as any).electronAPI !== undefined;
1351-
const isSupported = matomoDomains[window.location.hostname] || isElectron;
1395+
const isSupported = matomoDomains[window.location.hostname];
13521396

13531397
if (!isSupported) {
13541398
return false;

apps/remix-ide/src/app/plugins/electron/compilerLoaderPlugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export class compilerLoaderPlugin extends Plugin {
5151
export class compilerLoaderPluginDesktop extends ElectronPlugin {
5252
constructor() {
5353
super(profile)
54-
this.methods = []
54+
this.methods = methods
5555
}
5656

5757
async onActivation(): Promise<void> {

apps/remix-ide/src/app/plugins/electron/electronConfigPlugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ export class electronConfig extends ElectronPlugin {
77
name: 'electronconfig',
88
description: 'electronconfig',
99
})
10-
this.methods = []
10+
this.methods = ['readConfig']
1111
}
1212
}

apps/remix-ide/src/app/plugins/templates-selection/templates-selection-plugin.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,10 @@ export class TemplatesSelectionPlugin extends ViewPlugin {
262262
item.templateType = TEMPLATE_METADATA[item.value]
263263

264264
if (item.templateType && item.templateType.desktopCompatible === false && isElectron()) {
265-
return (<></>)
265+
return <React.Fragment key={item.name || index}></React.Fragment>
266266
}
267267

268-
if (item.templateType && item.templateType.disabled === true) return
268+
if (item.templateType && item.templateType.disabled === true) return null
269269
if (!item.opts) {
270270
return (
271271
<RemixUIGridCell

apps/remixdesktop/TEST.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99

1010
### Executables
1111

12-
Testing runs through nightwatch that treats an electron application as a special version of chrome, which it is. It basically calls the executable which is built by the ./rundist.bash script which creates executables based on the current channel configuration package.json, ie "version": "1.0.8-insiders"
12+
Testing runs through nightwatch that treats an electron application as a special version of chrome, which it is. It basically calls the executable which is built by the yarn dist -c test_only.json script which creates executables based on the current channel configuration package.json, ie "version": "1.0.8-insiders"
1313

1414
Executables are stored in the ./release directory. Without that executable you cannot run tests. You cannot call tests on local development instance that is launched by yarn start:dev. You need to create an executable file first.
1515

16-
This is done by running ./rundist.bash
16+
This is done by running yarn dist -c test_only.json
1717

1818
Normally when you would do a 'real' release you would package remix IDE into the distributable but for local e2e this is not necessary because it will use the remix IDE that is being served.
1919

@@ -40,7 +40,7 @@ In order to facilitate local testing nightwatch will boot the executable with th
4040
So to start testing locally
4141
- run the IDE with 'yarn serve' as you would normally do.
4242
- build your release. You will always need to do this when the Desktop app itself changes its code.
43-
- ./rundist.bash
43+
- yarn dist -c test_only.json
4444
- in apps/remixdesktop:
4545
- yarn build:e2e
4646
- run the test you want, refer to the actual JS that is build, not the TS, ie

0 commit comments

Comments
 (0)