Skip to content

Commit fb4a1ff

Browse files
committed
v3.1.0: UI overhaul - pagination, session actions modal, manage tab rework
- Current tab: sticky pagination, scroll-to-page, height increased - Session card: datetime + cookie count, removed URL - Manage tab: reordered cards, icon colors fixed, Quick Action revamped - Session Actions modal: new info card (data+expiry combined), flat buttons - Expiration dropdown: pill-based cookie list with status badges - Randomized storage key on first install, auto-migration from old key - JSON exports: raw array format, removed wrapper - Codebase cleanup: shared utils (DOM.closeModal, debounceInput, Normalize) - Bumped version to 3.1.0 Co-authored-by: risunCode <risunCode@users.noreply.github.com>
1 parent 3adbb02 commit fb4a1ff

18 files changed

Lines changed: 1009 additions & 690 deletions

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,32 @@
11
# Changelog
22

3+
## v3.1.0 (2026-03-21)
4+
5+
### Codebase Cleanup & Refactor
6+
- **Removed duplicate logic** — Unified modal close animation (`DOM.closeModal`), expiration calculation, and domain matching into shared utils
7+
- **Deleted dead code** — Removed unused `getExpirationStatus()`, duplicate `SessionStorage.getGrouped()`, unused `Renderer.pagination()`, and redundant `extractDomain()` wrapper
8+
- **Centralized import parsing** — New `Normalize.importSessions()` handles all formats (raw array, legacy wrapper, single object) in one place
9+
- **Merged BrowserStorage**`getLocal()` and `getSession()` now share a single `get(tabId, type)` implementation
10+
- **Extracted cookie restore helper**`cleanForRestore()` and `getCookieUrl()` centralize backward-compat scrubbing for Chrome's cookies API
11+
12+
### Security & Storage
13+
- **Randomized storage key** — Session data key is now a random hex string generated on first install instead of a hardcoded value. Migration from old key is automatic with zero user interaction
14+
- **Consistent domain matching** — All domain comparisons now use the shared `Domain.isMatch()` util instead of scattered manual implementations
15+
16+
### Export Format
17+
- **Raw JSON exports** — All JSON exports now output a plain session array instead of the proprietary `{version, exportDate, sessions}` wrapper. Import still accepts both formats for backward compatibility
18+
- **Label cleanup** — "Export JSON (raw)" → "Export JSON", "Copy Raw JSON" → "Copy JSON"
19+
20+
### UI Fixes
21+
- **Saved Data modal** — Fixed tab buttons (Cookies/localStorage/sessionStorage) overlapping by adjusting modal width and tab sizing
22+
- **No more alert()** — Replaced remaining `alert()` calls in export flow with proper modal dialogs
23+
24+
### Developer Experience
25+
- **Shared debounce helper**`DOM.debounceInput()` replaces copy-pasted debounce patterns in search handlers
26+
- **Cleaner utils** — Added `DOM.closeModal()`, `DOM.debounceInput()`, and `Normalize` module to `utils.js`
27+
28+
---
29+
330
## v3.0.0 (2026-02-07)
431

532
### New Features

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ Chrome extension for saving and restoring login sessions. Captures cookies, loca
77
- **Save & Restore** — Save complete login sessions and restore them later
88
- **Smart Expiration** — Tracks cookie expiration based on longest-lasting cookie *(experimental, may be inaccurate)*
99
- **Domain Groups** — Auto-groups sessions by domain for easy management
10-
- **Backup/Export** — JSON (plain) or OWI (AES-256 encrypted) formats
10+
- **Backup/Export** — JSON (raw array) or OWI (AES-256 encrypted) formats
1111
- **Batch Operations** — Bulk backup, delete expired, or clean by domain
1212
- **Clean Tab** — Selectively clear cookies, storage, history, and cache
13+
- **Randomized Storage** — Session data stored under a unique random key per installation
1314

1415
## Showcase
1516

package-lock.json

Lines changed: 2 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "seswi-session-manager",
3-
"version": "3.0.0",
3+
"version": "3.1.0",
44
"description": "Advanced Session Manager Chrome Extension",
55
"type": "module",
66
"scripts": {

src/background.js

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,41 @@
11
/**
22
* SesWi Service Worker
3+
* Generates a randomized storage key on first install for session data.
34
*/
45

5-
chrome.runtime.onInstalled.addListener(() => {
6-
console.log('[SesWi] Extension installed');
6+
const META_KEY = '_seswi_meta';
7+
const OLD_KEY = 'seswi-sessions-blyat';
8+
9+
function generateStorageKey() {
10+
const rand = crypto.getRandomValues(new Uint8Array(8));
11+
const hex = Array.from(rand, b => b.toString(16).padStart(2, '0')).join('');
12+
return `seswi-${hex}`;
13+
}
14+
15+
chrome.runtime.onInstalled.addListener(async () => {
16+
try {
17+
const result = await chrome.storage.local.get([META_KEY, OLD_KEY]);
18+
const meta = result[META_KEY];
19+
20+
// Already initialized — skip
21+
if (meta?.storageKey) return;
22+
23+
const newKey = generateStorageKey();
24+
25+
// Migrate data from old hardcoded key if it exists
26+
const oldData = result[OLD_KEY];
27+
if (Array.isArray(oldData) && oldData.length > 0) {
28+
await chrome.storage.local.set({
29+
[META_KEY]: { storageKey: newKey, createdAt: Date.now() },
30+
[newKey]: oldData
31+
});
32+
await chrome.storage.local.remove(OLD_KEY);
33+
} else {
34+
await chrome.storage.local.set({
35+
[META_KEY]: { storageKey: newKey, createdAt: Date.now() }
36+
});
37+
}
38+
} catch (e) {
39+
console.error('[SesWi] Init failed:', e);
40+
}
741
});

src/core/cookies.js

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,31 @@ function chunk(arr, size) {
1313
return out;
1414
}
1515

16+
function getCookieUrl(cookie) {
17+
const d = cookie.domain.startsWith('.') ? cookie.domain.slice(1) : cookie.domain;
18+
return `http${cookie.secure ? 's' : ''}://${d}${cookie.path}`;
19+
}
20+
21+
/**
22+
* Clean a saved cookie object for Chrome's cookies.set() API.
23+
* Removes properties that Chrome doesn't accept and handles backward compat.
24+
*/
25+
function cleanForRestore(cookie) {
26+
const clean = { ...cookie };
27+
if (cookie.hostOnly) delete clean.domain;
28+
if (cookie.session) delete clean.expirationDate;
29+
delete clean.hostOnly;
30+
delete clean.session;
31+
return clean;
32+
}
33+
1634
export const Cookies = {
1735
async getForDomain(domain) {
1836
try {
1937
const cookies = await chrome.cookies.getAll({});
2038
const filtered = cookies.filter(c => {
2139
const d = c.domain.startsWith('.') ? c.domain.slice(1) : c.domain;
22-
// Fix: exact match OR subdomain match (must have dot before domain)
23-
return d === domain || d.endsWith('.' + domain);
40+
return Domain.isMatch(domain, d);
2441
});
2542
return Response.success(filtered);
2643
} catch (e) {
@@ -43,21 +60,20 @@ export const Cookies = {
4360
try {
4461
const { data: cookies } = await this.getForDomain(domain);
4562
let count = 0;
46-
63+
4764
for (const part of chunk(cookies, CHUNK_SIZE)) {
4865
await Promise.all(part.map(async (cookie) => {
4966
try {
50-
const d = cookie.domain.startsWith('.') ? cookie.domain.slice(1) : cookie.domain;
5167
await chrome.cookies.remove({
52-
url: `http${cookie.secure ? 's' : ''}://${d}${cookie.path}`,
68+
url: getCookieUrl(cookie),
5369
name: cookie.name,
5470
storeId: cookie.storeId
5571
});
5672
count++;
5773
} catch {}
5874
}));
5975
}
60-
76+
6177
return Response.success(count);
6278
} catch (e) {
6379
return Response.error(e, 'Cookies.removeForDomain');
@@ -67,30 +83,24 @@ export const Cookies = {
6783
async restore(session) {
6884
try {
6985
if (!session.cookies?.length) return Response.error('No cookies to restore');
70-
86+
7187
// Clear existing first
7288
await this.removeForDomain(session.domain);
73-
89+
7490
let count = 0;
7591
for (const part of chunk(session.cookies, CHUNK_SIZE)) {
7692
await Promise.all(part.map(async (cookie) => {
7793
try {
78-
const d = cookie.domain.startsWith('.') ? cookie.domain.slice(1) : cookie.domain;
79-
const clean = { ...cookie };
80-
if (cookie.hostOnly) delete clean.domain;
81-
if (cookie.session) delete clean.expirationDate;
82-
delete clean.hostOnly;
83-
delete clean.session;
84-
94+
const clean = cleanForRestore(cookie);
8595
await chrome.cookies.set({
86-
url: `http${cookie.secure ? 's' : ''}://${d}${cookie.path}`,
96+
url: getCookieUrl(cookie),
8797
...clean
8898
});
8999
count++;
90100
} catch {}
91101
}));
92102
}
93-
103+
94104
return Response.success({ restored: count, total: session.cookies.length });
95105
} catch (e) {
96106
return Response.error(e, 'Cookies.restore');

src/core/crypto.js

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Handles: OWI encryption/decryption
44
*/
55

6-
import { Response, Logger } from '../utils.js';
6+
import { Response, Logger, Normalize } from '../utils.js';
77

88
// SJCL loaded globally from lib/sjcl.min.js
99
const getSJCL = () => window.sjcl;
@@ -64,16 +64,9 @@ export const Crypto = {
6464
}
6565

6666
const data = this.decrypt(parsed.encryptedData, password);
67-
68-
// Normalize to { sessions: [...] }
69-
if (parsed.type === 'single') {
70-
return Response.success({ sessions: [data] });
71-
}
72-
if (parsed.type === 'multi' && Array.isArray(data.sessions)) {
73-
return Response.success({ sessions: data.sessions });
74-
}
75-
76-
return Response.error('Unknown OWI format');
67+
const sessions = Normalize.importSessions(data);
68+
if (!sessions.length) return Response.error('No sessions found in OWI file');
69+
return Response.success({ sessions });
7770
} catch (e) {
7871
Logger.error('importOWI failed:', e);
7972
return Response.error(e, 'Crypto.importOWI');

src/core/icons.js

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,6 @@
55

66
import { Domain } from '../utils.js';
77

8-
// Helper to extract domain from URL
9-
function extractDomain(url) {
10-
try {
11-
return Domain.getBase(url);
12-
} catch {
13-
return null;
14-
}
15-
}
16-
178
class TabIcons {
189
constructor() {
1910
this.domainToIcon = {};
@@ -55,7 +46,7 @@ class TabIcons {
5546
try {
5647
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
5748
if (changeInfo.favIconUrl || changeInfo.url) {
58-
const domain = tab?.url ? extractDomain(tab.url) : null;
49+
const domain = tab?.url ? (() => { try { return Domain.getBase(tab.url); } catch { return null; } })() : null;
5950
if (domain && tab.favIconUrl) {
6051
this._set(domain, tab.favIconUrl);
6152
}
@@ -112,7 +103,8 @@ class TabIcons {
112103
const tabs = await chrome.tabs.query({});
113104
for (const tab of tabs) {
114105
if (!tab.url) continue;
115-
const domain = extractDomain(tab.url);
106+
let domain = null;
107+
try { domain = Domain.getBase(tab.url); } catch {}
116108
const icon = tab.favIconUrl || '';
117109
if (domain && icon) {
118110
this._set(domain, icon);

0 commit comments

Comments
 (0)