Skip to content

Commit d55db9d

Browse files
committed
Experimental: Collect the tab title of homepage site as its alias (#34)
1 parent 3bfc7dd commit d55db9d

File tree

8 files changed

+181
-8
lines changed

8 files changed

+181
-8
lines changed

src/background/icon-url-collector.ts renamed to src/background/icon-and-alias-collector.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
1+
import { DomainSource } from "../entity/dto/domain-alias"
2+
import DomainAliasDatabase from "../database/domain-alias-database"
13
import IconUrlDatabase from "../database/icon-url-database"
24
import { IS_CHROME } from "../util/constant/environment"
35
import { iconUrlOfBrowser } from "../util/constant/url"
4-
import { extractHostname } from "../util/pattern"
6+
import { extractHostname, isHomepage } from "../util/pattern"
57

68
const iconUrlDatabase = new IconUrlDatabase(chrome.storage.local)
9+
const domainAliasDatabase = new DomainAliasDatabase(chrome.storage.local)
10+
11+
function detectAlias(domain: string, tab: chrome.tabs.Tab) {
12+
let title = tab.title
13+
if (!title) return
14+
if (title.includes('-')) {
15+
title = title.split('-').map(a => a.trim()).sort((a, b) => a.length - b.length)[0]
16+
}
17+
if (title.includes('|')) {
18+
title = title.split('|').map(a => a.trim()).sort((a, b) => a.length - b.length)[0]
19+
}
20+
domainAliasDatabase.update({ name: title, domain, source: DomainSource.DETECTED })
21+
}
722

823
/**
924
* Process the tab
@@ -21,6 +36,8 @@ async function processTabInfo(tab: chrome.tabs.Tab): Promise<void> {
2136
IS_CHROME && /^localhost(:.+)?/.test(domain) && (favIconUrl = undefined)
2237
const iconUrl = favIconUrl || await iconUrlOfBrowser(protocol, domain)
2338
iconUrlDatabase.put(domain, iconUrl)
39+
40+
isHomepage(url) && detectAlias(domain, tab)
2441
}
2542

2643
/**
@@ -41,8 +58,8 @@ function listen() {
4158
/**
4259
* Collect the favicon of host
4360
*/
44-
class IconUrlCollector {
61+
class IconAndAliasCollector {
4562
listen = listen
4663
}
4764

48-
export default IconUrlCollector
65+
export default IconAndAliasCollector

src/background/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { openLog } from '../common/logger'
22
import WhitelistMenuManager from './whitelist-menu-manager'
33
import BrowserActionMenuManager from './browser-action-menu-manager'
4-
import IconUrlCollector from './icon-url-collector'
4+
import IconAndAliasCollector from './icon-and-alias-collector'
55
import MessageListener from './message-listener'
66
import Timer from './timer'
77
import VersionManager from './version-manager'
@@ -14,8 +14,8 @@ openLog()
1414
// Start the timer
1515
new Timer().start()
1616

17-
// Collect the icon url
18-
new IconUrlCollector().listen()
17+
// Collect the icon url and title
18+
new IconAndAliasCollector().listen()
1919

2020
// Message listener
2121
new MessageListener().listen()

src/database/common/storage-promise.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,26 @@ export default class StoragePromise {
1212
return new Promise(resolve => this.storage.get(keys, resolve))
1313
}
1414

15+
/**
16+
* @since 0.4.1
17+
*/
18+
async getOne(key: string): Promise<any | undefined> {
19+
return (await this.get(key))[key]
20+
}
21+
1522
set(obj: Object): Promise<void> {
1623
return new Promise<void>(resolve => this.storage.set(obj, resolve))
1724
}
1825

26+
/**
27+
* @since 0.4.1
28+
*/
29+
put(key: string, val: Object): Promise<void> {
30+
const toUpdate = {}
31+
toUpdate[key] = val
32+
return this.set(toUpdate)
33+
}
34+
1935
remove(key: string | string[]): Promise<void> {
2036
return new Promise(resolve => this.storage.remove(key, resolve))
2137
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* @author zhy
3+
* @since 0.4.1
4+
*/
5+
import { DomainAlias, DomainSource } from "../entity/dto/domain-alias"
6+
import BaseDatabase from "./common/base-database"
7+
import { REMAIN_WORD_PREFIX } from "./common/constant"
8+
9+
const DB_KEY_PREFIX = REMAIN_WORD_PREFIX + "ALIAS"
10+
const DB_KEY_PREFIX_LENGTH = DB_KEY_PREFIX.length
11+
12+
const SOURCE_PREFIX_MAP: { [source in DomainSource]: string } = {
13+
USER: 'u',
14+
DETECTED: 'd'
15+
}
16+
const ABBR_MAP = {
17+
'u': DomainSource.USER,
18+
'd': DomainSource.DETECTED
19+
}
20+
21+
const generateKey = (domain: string) => DB_KEY_PREFIX + domain
22+
const domainOf = (key: string) => key.substring(DB_KEY_PREFIX_LENGTH)
23+
function valueOf(domain: string, value: string): DomainAlias {
24+
const abbr = value.substr(0, 1)
25+
26+
return {
27+
domain,
28+
source: ABBR_MAP[abbr],
29+
name: value.substr(1)
30+
}
31+
}
32+
33+
class DomainAliasDatabase extends BaseDatabase {
34+
35+
/**
36+
* Update the alias
37+
*/
38+
async update(toUpdate: DomainAlias): Promise<void> {
39+
const { domain, name, source } = toUpdate
40+
const key = generateKey(domain)
41+
const value = SOURCE_PREFIX_MAP[source] + name
42+
if (source === DomainSource.USER) {
43+
// Force update
44+
return this.storage.put(key, value)
45+
}
46+
const existVal = this.storage.getOne(key)
47+
if (!existVal || typeof existVal !== 'string') {
48+
// Force update
49+
return this.storage.put(key, value)
50+
}
51+
const abbr = (existVal as string).substring(0, 1)
52+
if (ABBR_MAP[abbr] === DomainSource.DETECTED) {
53+
// Update
54+
return this.storage.put(key, value)
55+
}
56+
}
57+
58+
async get(...domains: string[]): Promise<{ [host: string]: DomainAlias }> {
59+
const keys = domains.map(generateKey)
60+
const items = await this.storage.get(keys)
61+
const result = {}
62+
Object.entries(items).forEach(([key, value]) => {
63+
const domain = domainOf(key)
64+
result[domain] = valueOf(domain, value)
65+
})
66+
return Promise.resolve(result)
67+
}
68+
69+
async importData(data: any): Promise<void> {
70+
const items = await this.storage.get()
71+
const toSave = {}
72+
Object.entries(data)
73+
.filter(([key, value]) => key.startsWith(DB_KEY_PREFIX) && !!value && typeof value === 'string')
74+
.forEach(([key, value]) => toSave[key] = this.migrate(items[key], value as string))
75+
await this.storage.set(toSave)
76+
}
77+
78+
private migrate(exist: string | undefined, toUpdate: string): string {
79+
if (!exist) {
80+
return toUpdate
81+
}
82+
if (exist.startsWith('u') && !toUpdate.startsWith('u')) {
83+
return exist
84+
}
85+
return toUpdate
86+
}
87+
}
88+
89+
export default DomainAliasDatabase

src/entity/dto/domain-alias.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* @since 0.4.1
3+
* @author zhy
4+
*/
5+
export enum DomainSource {
6+
USER = 'USER', // By user
7+
DETECTED = 'DETECTED' // Auto-detected
8+
}
9+
10+
export type DomainAlias = {
11+
domain: string
12+
name: string
13+
source: DomainSource
14+
}

src/service/immigration.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import PeriodDatabase from '../database/period-database'
99
import RouterDatabase from '../database/router-database'
1010
import TimerDatabase from '../database/timer-database'
1111
import WhitelistDatabase from '../database/whitelist-database'
12+
import DomainAliasDatabase from '../database/domain-alias-database'
1213

1314
type MetaInfo = {
1415
version: string
@@ -28,7 +29,8 @@ function initDatabase(storage: chrome.storage.StorageArea): BaseDatabase[] {
2829
new LimitDatabase(storage),
2930
new MergeRuleDatabase(storage),
3031
new WhitelistDatabase(storage),
31-
new RouterDatabase(storage)
32+
new RouterDatabase(storage),
33+
new DomainAliasDatabase(storage)
3234
]
3335

3436
return result

src/util/pattern.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,26 @@ export function extractHostname(url: string): HostInfo {
8080
return { host, protocol }
8181
}
8282

83+
/**
84+
* Judge whether homepage
85+
* e.g.
86+
* 1. https://baidu.com/ = true
87+
* 2. http://baidu.com = true
88+
* 3. www.baidu.com = true
89+
* 4. https://baidu.com/a = false
90+
* 5. http://qq.com?a=1 = false
91+
*
92+
* @since 0.4.1
93+
*/
94+
export function isHomepage(url: string) {
95+
if (url.includes('?')) {
96+
return false
97+
}
98+
const indexOfDoubleSlashes = url.indexOf("//")
99+
let hostStr = url
100+
if (indexOfDoubleSlashes > -1) {
101+
hostStr = url.substr(indexOfDoubleSlashes + 2)
102+
}
103+
const indexOfSlash = hostStr.indexOf("/")
104+
return indexOfSlash < 0 || indexOfSlash === hostStr.length - 1
105+
}

test/util/pattern.test.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { extractHostname, isBrowserUrl, isIpAndPort, isValidMergeOriginHost } from "../../src/util/pattern"
1+
import { extractHostname, isBrowserUrl, isHomepage, isIpAndPort, isValidMergeOriginHost } from "../../src/util/pattern"
22

33
test('browser url', () => {
44
// chrome
@@ -40,4 +40,16 @@ test("url", () => {
4040
expect(extractHostname('http://localhost:8087/')).toEqual({ host: 'localhost:8087', protocol: 'http' })
4141
expect(extractHostname('http://localhost:8087/?123=123')).toEqual({ host: 'localhost:8087', protocol: 'http' })
4242
expect(extractHostname('localhost:8087/?123=123')).toEqual({ host: 'localhost:8087', protocol: '' })
43+
})
44+
45+
test("homepage", () => {
46+
expect(isHomepage("http://baidu.com")).toBeTruthy()
47+
expect(isHomepage("http://baidu.com/")).toBeTruthy()
48+
expect(isHomepage("https://baidu.com/")).toBeTruthy()
49+
expect(isHomepage("baidu.com/")).toBeTruthy()
50+
51+
expect(isHomepage("baidu.com/a")).toBeFalsy()
52+
expect(isHomepage("https://baidu.com/a")).toBeFalsy()
53+
expect(isHomepage("https://baidu.com/a?a=2")).toBeFalsy()
54+
expect(isHomepage("https://baidu.com?a=2")).toBeFalsy()
4355
})

0 commit comments

Comments
 (0)