my-provider-dash-manager 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +9 -1
  2. package/package.json +1 -1
  3. package/src/main.js +66 -1
package/README.md CHANGED
@@ -72,6 +72,11 @@ Each provider lists its accounts; hovering one reveals open / sleep / close / fi
72
72
 
73
73
  ![A dashboard open in the app](docs/screenshots/dashboard.png)
74
74
 
75
+ Browser controls appear in the strip while a tab is open: back, forward, reload, dashboard, and an
76
+ address bar that navigates inside that account's own session.
77
+
78
+ ![Navigation bar with a Gmail sign-in open](docs/screenshots/navigation.png)
79
+
75
80
  **The top bar is only your open tabs** — filter them, or group them by provider with collapsible sections.
76
81
  Slept tabs stay in the strip (dimmed) and resume on click.
77
82
 
@@ -126,7 +131,10 @@ sessions, one place.
126
131
  hover actions: open, sleep, close, fill login, connect an API token, manage. Collapse any provider.
127
132
  - **Top bar — open tabs only.** A filter box and a group-by-provider toggle with collapsible groups.
128
133
  - **Home** is always one click (or `Esc`) away: a grid of every service and account with live status.
129
- - Keyboard: `Ctrl+W` sleep, `Ctrl+Shift+W` sleep all, `Ctrl+Tab` cycle, `Ctrl+1…9` jump.
134
+ - **Browser controls** while a tab is open: back, forward, reload/stop, back-to-dashboard, and an
135
+ editable address bar that navigates inside that account's own session (http/https only).
136
+ - Keyboard: `Ctrl+W` sleep, `Ctrl+Shift+W` sleep all, `Ctrl+Tab` cycle, `Ctrl+1…9` jump,
137
+ `Alt+Left`/`Alt+Right` back/forward, `Ctrl+R`/`F5` reload, `Alt+Home` dashboard, `Ctrl+L` address.
130
138
 
131
139
  ### Saved logins
132
140
  - **Remembers logins you type** inside an account tab, bound to *that* account, so two accounts on the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-provider-dash-manager",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Manage many accounts per cloud service (Vercel, Netlify, Supabase, Cloudflare, Railway, Render, GitHub) in one desktop app \u2014 isolated persistent sessions, one tab per account.",
5
5
  "main": "src/main.js",
6
6
  "scripts": {
package/src/main.js CHANGED
@@ -282,6 +282,8 @@ class ViewManager {
282
282
  };
283
283
  view.webContents.on('did-navigate', onNav);
284
284
  view.webContents.on('did-navigate-in-page', onNav); // dashboards route client-side (pushState) — must track too
285
+ view.webContents.on('did-start-loading', () => this.emitState());
286
+ view.webContents.on('did-stop-loading', () => this.emitState());
285
287
  view.webContents.on('render-process-gone', () => {
286
288
  console.error(`renderer gone: ${key}`);
287
289
  this.hibernate(key); // drop the dead view; partition survives, reopening rehydrates
@@ -439,12 +441,43 @@ class ViewManager {
439
441
  key: k, svc: s.svc, id: s.id, label: a.label || s.id, colorIdx: a.colorIdx || 0,
440
442
  title: t ? t.title : '', url: t ? t.url : s.url,
441
443
  live: !!t, visible: t ? t.visible : false,
444
+ canGoBack: !!(t && !t.view.webContents.isDestroyed() && t.view.webContents.navigationHistory.canGoBack()),
445
+ canGoForward: !!(t && !t.view.webContents.isDestroyed() && t.view.webContents.navigationHistory.canGoForward()),
446
+ loading: !!(t && !t.view.webContents.isDestroyed() && t.view.webContents.isLoading()),
442
447
  lastState: t ? t.lastState : (a.lastState || 'unknown'),
443
448
  };
444
449
  }),
445
450
  });
446
451
  }
447
452
 
453
+ // Browser-style navigation for the ACTIVE tab. `navigate` accepts http(s) only — the address bar
454
+ // must never become a way to launch other schemes from the chrome.
455
+ nav(action, url) {
456
+ const t = this.tabs.get(this.activeKey);
457
+ if (!t || t.view.webContents.isDestroyed()) return false;
458
+ const wc = t.view.webContents;
459
+ switch (action) {
460
+ case 'back': if (wc.navigationHistory.canGoBack()) wc.navigationHistory.goBack(); break;
461
+ case 'forward': if (wc.navigationHistory.canGoForward()) wc.navigationHistory.goForward(); break;
462
+ case 'reload': wc.reload(); break;
463
+ case 'stop': wc.stop(); break;
464
+ case 'dashboard': { const svc = serviceByKey(t.svc); if (svc) wc.loadURL(svc.dashboardUrl).catch(() => {}); break; }
465
+ case 'navigate': {
466
+ let target = String(url || '').trim();
467
+ if (!target) return false;
468
+ if (!/^[a-z][a-z0-9+.-]*:/i.test(target)) target = `https://${target}`; // "example.com/path" → https
469
+ let parsed;
470
+ try { parsed = new URL(target); } catch { return false; }
471
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') return false;
472
+ wc.loadURL(parsed.href).catch(err => console.error(`navigate failed ${this.activeKey}:`, err.message));
473
+ break;
474
+ }
475
+ default: return false;
476
+ }
477
+ this.emitState();
478
+ return true;
479
+ }
480
+
448
481
  snapshotMemory() {
449
482
  // Summing workingSetSize over-reports badly: Chromium maps shared pages (the framework binary and
450
483
  // friends) into every process, so they are counted once per process — with 10 processes that read
@@ -575,6 +608,25 @@ async function runSmoke() {
575
608
  const mem = vm.snapshotMemory();
576
609
  check('live-tabs', mem.liveTabs === 3, `${mem.totalMB}MB`);
577
610
 
611
+ // browser-style navigation on the active tab (address bar, back, dashboard) — http(s) only
612
+ const key = accountKey('netlify', 'a1');
613
+ vm.activate(key);
614
+ const urlOf = () => vm.tabs.get(key).view.webContents.getURL();
615
+ const waitFor = async (pred, ms = 15000) => { const t0 = Date.now(); while (Date.now() - t0 < ms) { if (pred()) return true; await sleep(250); } return pred(); };
616
+ check('nav-rejects-javascript-scheme', vm.nav('navigate', 'javascript:alert(1)') === false && !urlOf().startsWith('javascript:'));
617
+ check('nav-rejects-file-scheme', vm.nav('navigate', 'file:///C:/Windows') === false && !urlOf().startsWith('file:'));
618
+ check('nav-rejects-empty', vm.nav('navigate', ' ') === false);
619
+ vm.nav('navigate', 'example.com'); // bare host → https
620
+ check('nav-address-goes-there', await waitFor(() => /^https:\/\/example\.com\/?$/.test(urlOf())), urlOf());
621
+ check('nav-can-go-back', vm.tabs.get(key).view.webContents.navigationHistory.canGoBack());
622
+ vm.nav('back');
623
+ check('nav-back-returns', await waitFor(() => /netlify\.com/.test(urlOf())), urlOf());
624
+ vm.nav('navigate', 'https://example.org/');
625
+ await waitFor(() => /example\.org/.test(urlOf()));
626
+ vm.nav('dashboard');
627
+ check('nav-dashboard-returns-to-service', await waitFor(() => /app\.netlify\.com/.test(urlOf())), urlOf());
628
+ check('nav-noop-without-active-tab', (() => { const k = vm.activeKey; vm.activeKey = null; const r = vm.nav('reload'); vm.activeKey = k; return r === false; })());
629
+
578
630
  fs.writeFileSync(path.join(__dirname, '..', 'smoke-report.json'), JSON.stringify(results, null, 2));
579
631
  app.exit(results.checks.every(c => c.ok) ? 0 : 1);
580
632
  }
@@ -938,8 +990,20 @@ function runInteractive(captureMode = false) {
938
990
  })),
939
991
  ],
940
992
  },
993
+ {
994
+ label: 'Navigate',
995
+ submenu: [
996
+ { label: 'Back', accelerator: 'Alt+Left', click: () => vm.nav('back') },
997
+ { label: 'Forward', accelerator: 'Alt+Right', click: () => vm.nav('forward') },
998
+ { label: 'Reload', accelerator: 'CmdOrCtrl+R', click: () => vm.nav('reload') },
999
+ { label: 'Reload', accelerator: 'F5', click: () => vm.nav('reload') },
1000
+ { label: 'Dashboard', accelerator: 'Alt+Home', click: () => vm.nav('dashboard') },
1001
+ { type: 'separator' },
1002
+ { label: 'Focus Address Bar', accelerator: 'CmdOrCtrl+L', click: () => { if (win.webContents && !win.webContents.isDestroyed()) win.webContents.send('focus-address'); } },
1003
+ ],
1004
+ },
941
1005
  { label: 'Edit', submenu: [{ role: 'copy' }, { role: 'paste' }, { role: 'selectAll' }] },
942
- { label: 'View', submenu: [{ role: 'reload' }, { role: 'forceReload' }, { type: 'separator' }, { role: 'zoomIn' }, { role: 'zoomOut' }, { role: 'zoomReset' }, { type: 'separator' }, { role: 'toggleDevTools' }] },
1006
+ { label: 'View', submenu: [{ role: 'zoomIn' }, { role: 'zoomOut' }, { role: 'zoomReset' }, { type: 'separator' }, { role: 'toggleDevTools' }] },
943
1007
  ]);
944
1008
  Menu.setApplicationMenu(menu);
945
1009
 
@@ -989,6 +1053,7 @@ function runInteractive(captureMode = false) {
989
1053
  return key;
990
1054
  });
991
1055
  ipcMain.handle('activate-tab', (_e, key) => vm.activate(key));
1056
+ ipcMain.handle('nav', (_e, action, url) => vm.nav(String(action || ''), url));
992
1057
  ipcMain.handle('sleep-tab', (_e, key) => vm.hibernate(key));
993
1058
  ipcMain.handle('sleep-all', () => vm.hibernateAll());
994
1059
  ipcMain.handle('update-account', (_e, svc, id, patch) => vm.updateAccount(svc, id, patch || {}));