Skip to content

Commit d64a674

Browse files
committed
fix: filter outdated extensions
1 parent 610dfad commit d64a674

File tree

1 file changed

+65
-51
lines changed
  • packages/electron-chrome-web-store/src/browser

1 file changed

+65
-51
lines changed

packages/electron-chrome-web-store/src/browser/loader.ts

Lines changed: 65 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as fs from 'node:fs'
22
import * as path from 'node:path'
33

44
import { generateId } from './id'
5+
import { compareVersions } from './utils'
56

67
const d = require('debug')('electron-chrome-web-store:loader')
78

@@ -20,6 +21,25 @@ const manifestExists = async (dirPath: string) => {
2021
}
2122
}
2223

24+
/**
25+
* DFS directories for extension manifests.
26+
*/
27+
async function extensionSearch(dirPath: string, depth: number = 0): Promise<string[]> {
28+
if (depth >= 2) return []
29+
const results = []
30+
const dirEntries = await fs.promises.readdir(dirPath, { withFileTypes: true })
31+
for (const entry of dirEntries) {
32+
if (entry.isDirectory()) {
33+
if (await manifestExists(path.join(dirPath, entry.name))) {
34+
results.push(path.join(dirPath, entry.name))
35+
} else {
36+
results.push(...(await extensionSearch(path.join(dirPath, entry.name), depth + 1)))
37+
}
38+
}
39+
}
40+
return results
41+
}
42+
2343
/**
2444
* Discover list of extensions in the given path.
2545
*/
@@ -35,63 +55,27 @@ async function discoverExtensions(extensionsPath: string): Promise<ExtensionPath
3555
return []
3656
}
3757

38-
// Get top level directories
39-
const subDirectories = await fs.promises.readdir(extensionsPath, {
40-
withFileTypes: true,
41-
})
42-
43-
// Find all directories containing extension manifest.json
44-
// Limits search depth to 1-2.
45-
const extensionDirectories = await Promise.all(
46-
subDirectories
47-
.filter((dirEnt) => dirEnt.isDirectory())
48-
.map(async (dirEnt) => {
49-
const extPath = path.join(extensionsPath, dirEnt.name)
50-
51-
// Check if manifest exists in root directory
52-
if (await manifestExists(extPath)) {
53-
return extPath
54-
}
55-
56-
// Check one level deeper
57-
const extSubDirs = await fs.promises.readdir(extPath, {
58-
withFileTypes: true,
59-
})
60-
61-
// Look for manifest in each subdirectory
62-
for (const subDir of extSubDirs) {
63-
if (!subDir.isDirectory()) continue
64-
65-
const subDirPath = path.join(extPath, subDir.name)
66-
if (await manifestExists(subDirPath)) {
67-
return subDirPath
68-
}
69-
}
70-
}),
71-
)
72-
58+
const extensionDirectories = await extensionSearch(extensionsPath)
7359
const results: ExtensionPathInfo[] = []
7460

7561
for (const extPath of extensionDirectories.filter(Boolean)) {
76-
console.log(`Loading extension from ${extPath}`)
7762
try {
7863
const manifestPath = path.join(extPath!, 'manifest.json')
7964
const manifestJson = (await fs.promises.readFile(manifestPath)).toString()
8065
const manifest: chrome.runtime.Manifest = JSON.parse(manifestJson)
81-
if (manifest.key) {
82-
results.push({
83-
type: 'store',
84-
path: extPath!,
85-
manifest,
86-
id: generateId(manifest.key),
87-
})
88-
} else {
89-
results.push({
90-
type: 'unpacked',
91-
path: extPath!,
92-
manifest,
93-
})
94-
}
66+
const result = manifest.key
67+
? {
68+
type: 'store' as const,
69+
path: extPath!,
70+
manifest,
71+
id: generateId(manifest.key),
72+
}
73+
: {
74+
type: 'unpacked' as const,
75+
path: extPath!,
76+
manifest,
77+
}
78+
results.push(result)
9579
} catch (e) {
9680
console.error(e)
9781
}
@@ -100,6 +84,35 @@ async function discoverExtensions(extensionsPath: string): Promise<ExtensionPath
10084
return results
10185
}
10286

87+
/**
88+
* Filter any outdated extensions in the case of duplicate installations.
89+
*/
90+
function filterOutdatedExtensions(extensions: ExtensionPathInfo[]): ExtensionPathInfo[] {
91+
const uniqueExtensions: ExtensionPathInfo[] = []
92+
const storeExtMap = new Map<ExtensionId, ExtensionPathInfo>()
93+
94+
for (const ext of extensions) {
95+
if (ext.type === 'unpacked') {
96+
// Unpacked extensions are always unique to their path
97+
uniqueExtensions.push(ext)
98+
} else if (!storeExtMap.has(ext.id)) {
99+
// New store extension
100+
storeExtMap.set(ext.id, ext)
101+
} else {
102+
// Existing store extension, compare with existing version
103+
const latestExt = storeExtMap.get(ext.id)!
104+
if (compareVersions(latestExt.manifest.version, ext.manifest.version) < 0) {
105+
storeExtMap.set(ext.id, ext)
106+
}
107+
}
108+
}
109+
110+
// Append up to date store extensions
111+
storeExtMap.forEach((ext) => uniqueExtensions.push(ext))
112+
113+
return uniqueExtensions
114+
}
115+
103116
/**
104117
* Load all extensions from the given directory.
105118
*/
@@ -110,7 +123,8 @@ export async function loadAllExtensions(
110123
allowUnpacked?: boolean
111124
} = {},
112125
) {
113-
const extensions = await discoverExtensions(extensionsPath)
126+
let extensions = await discoverExtensions(extensionsPath)
127+
extensions = filterOutdatedExtensions(extensions)
114128
d('discovered %d extension(s) in %s', extensions.length, extensionsPath)
115129

116130
for (const ext of extensions) {

0 commit comments

Comments
 (0)